This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit 4f3ca09260ca5ab9888b21454896acf281fec923
Author: Andy Seaborne <[email protected]>
AuthorDate: Thu Dec 19 10:44:12 2024 +0000

    GH-2902: FusekiServerCtl - Code for server on-disk state
---
 .../main/java/org/apache/jena/fuseki/Fuseki.java   |   1 +
 .../org/apache/jena/fuseki/build/FusekiConfig.java |   7 +-
 .../org/apache/jena/fuseki/ctl/ActionMetrics.java  |  11 +-
 .../jena/fuseki/metrics/MetricsProvider.java       |  35 ++-
 .../fuseki/metrics/MetricsProviderRegistry.java    |  68 ------
 .../PrometheusMetricsProvider.java                 |   4 +-
 .../jena/fuseki/metrics/SimpleMetricsProvider.java |   1 -
 .../fuseki/metrics/prometheus/InitPrometheus.java  |  38 ----
 .../apache/jena/fuseki/system/DataUploader.java    |   2 +-
 .../jena/fuseki/system/UploadDetailsWithName.java  |  32 ---
 .../apache/jena/fuseki/system/spot/SpotTDB2.java   |   1 -
 .../org.apache.jena.sys.JenaSubsystemLifecycle     |   1 -
 .../org/apache/jena/fuseki/main/FusekiServer.java  |  31 ++-
 .../apache/jena/fuseki/main/cmds/FusekiMain.java   |  27 +--
 .../jena/fuseki/main/cmds/FusekiServerCmd.java     |   5 +-
 .../jena/fuseki/main/sys/FusekiAutoModules.java    | 232 ++++++--------------
 .../apache/jena/fuseki/main/sys/FusekiModules.java |   9 +-
 .../apache/jena/fuseki/mgt/ActionBackupList.java   |   6 +-
 .../org/apache/jena/fuseki/mgt/ActionDatasets.java |  46 +---
 .../java/org/apache/jena/fuseki/mgt/Backup.java    |   2 +-
 .../mgt/{FusekiApp.java => FusekiServerCtl.java}   | 241 +++++++++++----------
 .../org/apache/jena/fuseki/mgt/ServerMgtConst.java |   2 +-
 .../java/org/apache/jena/fuseki/mgt/Template.java  |   2 +-
 .../apache/jena/fuseki/mod/FusekiModServer.java    |  90 ++++++++
 .../jena/fuseki/mod/admin/ArgModuleAdmin.java      |   4 +-
 .../apache/jena/fuseki/mod/admin/FMod_Admin.java   |  31 ++-
 .../org/apache/jena/fuseki/mod/package-info.java   |   2 -
 .../jena/fuseki/mod/prometheus/ActionMetrics.java  |  49 -----
 .../fuseki/mod/prometheus/FMod_Prometheus.java     |  15 +-
 .../apache/jena/fuseki/mod/shiro/FMod_Shiro.java   |  10 +-
 .../org/apache/jena/fuseki/mod/ui/FMod_UI.java     |   4 +-
 .../org/apache/jena/fuseki/server/config.ttl       |   2 +
 .../org/apache/jena/fuseki/TC_FusekiServer.java    |   4 +-
 .../org/apache/jena/fuseki/main/TS_FusekiMain.java |   4 +-
 .../apache/jena/fuseki/main/TestConfigFile.java    |   1 -
 .../org/apache/jena/fuseki/main/TestMetrics.java   |   8 +-
 ...fixesService.java => PrefixesServiceTests.java} |   3 +-
 .../apache/jena/fuseki/main/sys/LegacyModule.java  |  32 ---
 .../fuseki/main/sys/ModuleByServiceLoader.java     |   1 +
 .../apache/jena/fuseki/main/sys/ModuleForTest.java |   1 +
 .../jena/fuseki/main/sys/TestFusekiModules.java    |  36 +--
 .../org/apache/jena/fuseki/mod/TS_FusekiMods.java  |   9 +-
 .../apache/jena/fuseki/mod/TestFusekiServer.java   |   1 -
 .../jena/fuseki/mod/admin/TS_FusekiServerApp.java  |  32 ---
 .../apache/jena/fuseki/mod/admin/TestAdmin.java    |   4 +-
 .../fuseki/mod/admin/TestTemplateAddDataset.java   |   6 +-
 .../jena/fuseki/mod/metrics/TestModPrometheus.java |  16 +-
 .../apache/jena/fuseki/mod/shiro/TestModShiro.java |   8 +-
 .../org.apache.jena.fuseki.main.sys.FusekiModule   |   1 -
 jena-fuseki2/jena-fuseki-server/pom.xml            |  10 +-
 .../jena/fuseki/webapp/FusekiServerListener.java   |   8 +-
 51 files changed, 492 insertions(+), 704 deletions(-)

diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java
index e51b47bdc8..a8c4a0680c 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java
@@ -183,6 +183,7 @@ public class Fuseki {
     public static final String attrOperationRegistry       = 
"org.apache.jena.fuseki:OperationRegistry";
     public static final String attrAuthorizationService    = 
"org.apache.jena.fuseki:AuthorizationService";
     public static final String attrFusekiServer            = 
"org.apache.jena.fuseki:Server";
+    public static final String attrMetricsProvider         = 
"org.apache.jena.fuseki:MetricsProvider";
 
     public static void setVerbose(ServletContext cxt, boolean verbose) {
         cxt.setAttribute(attrVerbose, Boolean.valueOf(verbose));
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
index 8496ef7a94..2e6ec3a8ca 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
@@ -189,7 +189,6 @@ public class FusekiConfig {
     public static List<DataAccessPoint> processServerConfiguration(Graph 
configuration, Context context) {
         Node server = findServer(configuration);
         if ( server != null ) {
-            // XXX Temporary
             Resource rServer = resource(configuration, server);
             mergeContext(configuration,server, context);
             processLoadClass(configuration,server);
@@ -213,8 +212,6 @@ public class FusekiConfig {
     public static List<DataAccessPoint> processServerConfiguration(Model 
configuration, Context context) {
         return processServerConfiguration(configuration.getGraph(), context);
     }
-
-    // XXX Adapter
     /*package*/ static Resource resource(Graph graph, Node node) {
         Model m = ModelFactory.createModelForGraph(graph);
         RDFNode rNode = m.asRDFNode(node);
@@ -271,7 +268,7 @@ public class FusekiConfig {
     }
 
     /**
-     * Process any {@code ja:loadClass}
+     * Legacy support for {@code ja:loadClass}
      */
     public static void processLoadClass(Graph configuration, Node server) {
         if ( server == null )
@@ -633,7 +630,7 @@ public class FusekiConfig {
                     if ( named.size() > 1 )
                         throw new FusekiConfigException("Multiple property 
values for <"+FusekiVocabG.pEndpointName+"> with 
<"+endpointProperty.getURI()+"> for "+BuildLib.displayStr(configuration, svc));
                     endpointName = named.get(0).getLiteralLexicalForm();
-                    // XXX Necessary? check
+                    // Check for multiple
                     List<Node> x = G.listSP(configuration, ep, 
FusekiVocabG.pAllowedUsers);
                     if ( x.size() > 1 )
                         throw new FusekiConfigException("Multiple 
fuseki:"+FusekiVocabG.pAllowedUsers.getLocalName()+" for 
"+displayStr(configuration, ep));
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionMetrics.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionMetrics.java
index d488938e89..8565e8ae73 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionMetrics.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionMetrics.java
@@ -17,7 +17,9 @@
  */
 package org.apache.jena.fuseki.ctl;
 
-import org.apache.jena.fuseki.metrics.MetricsProviderRegistry;
+import org.apache.jena.atlas.logging.Log;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.metrics.MetricsProvider;
 import org.apache.jena.fuseki.servlets.ActionLib;
 import org.apache.jena.fuseki.servlets.HttpAction;
 import org.apache.jena.fuseki.servlets.ServletOps;
@@ -42,7 +44,12 @@ public class ActionMetrics extends ActionCtl {
 
     @Override
     public void execute(HttpAction action) {
-        MetricsProviderRegistry.get().scrape( action );
+        MetricsProvider metricsProvider = 
MetricsProvider.getMetricsProvider(getServletContext());
+        if ( metricsProvider == null ) {
+            Log.warn(Fuseki.actionLog, "No metrics provider");
+            ServletOps.errorOccurred("No metrics provider");
+        }
+        metricsProvider.scrape( action );
         ServletOps.success(action);
     }
 }
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java
index 853ed89fd5..2dcd79cea9 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java
@@ -17,12 +17,43 @@
  */
 package org.apache.jena.fuseki.metrics;
 
+import java.util.Objects;
+
 import io.micrometer.core.instrument.MeterRegistry;
+import jakarta.servlet.ServletContext;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry;
 import org.apache.jena.fuseki.servlets.HttpAction;
 
+/** Micrometer registry and output generator. */
 public interface MetricsProvider {
+    public MeterRegistry getMeterRegistry();
+    public void scrape(HttpAction action);
+
+    /** Bind each data access point in a DataAccessPointRegistry to the system 
Micrometer {@link MeterRegistry}. */
+    public default void dataAccessPointMetrics(MetricsProvider 
metricsProvider, DataAccessPointRegistry dapRegistry) {
+        try {
+            MeterRegistry meterRegistry = metricsProvider.getMeterRegistry();
+            if (meterRegistry != null) {
+                dapRegistry.accessPoints().forEach(dap->{
+                    new FusekiRequestsMetrics( dap ).bindTo( meterRegistry );
+                });
+            }
+        } catch (Throwable th) {
+            Fuseki.configLog.error("Failed to bind all data access points to 
netrics provider", th);
+        }
+    }
 
-    MeterRegistry getMeterRegistry();
-    void scrape(HttpAction action);
+    public static void setMetricsProvider(ServletContext servletContext, 
MetricsProvider provider) {
+        Objects.requireNonNull(servletContext);
+        if ( provider == null )
+            servletContext.removeAttribute(Fuseki.attrMetricsProvider);
+        else
+            servletContext.setAttribute(Fuseki.attrMetricsProvider, provider);
+    }
 
+    public static MetricsProvider getMetricsProvider(ServletContext 
servletContext) {
+        Objects.requireNonNull(servletContext);
+        return 
(MetricsProvider)servletContext.getAttribute(Fuseki.attrMetricsProvider);
+    }
 }
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProviderRegistry.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProviderRegistry.java
deleted file mode 100644
index cf37350da7..0000000000
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProviderRegistry.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jena.fuseki.metrics;
-
-import io.micrometer.core.instrument.MeterRegistry;
-import org.apache.jena.fuseki.Fuseki;
-import org.apache.jena.fuseki.server.DataAccessPointRegistry;
-
-public class MetricsProviderRegistry {
-
-    private static int priority = Integer.MAX_VALUE;
-    private static MetricsProvider metricsProvider = new 
SimpleMetricsProvider();
-
-    public static MetricsProvider get() {
-        return metricsProvider;
-    }
-
-    /** @deprecated Use {@link #set(MetricsProvider)} */
-    @Deprecated(forRemoval = true)
-    public static void put(MetricsProvider metricsProvider, int priority) {
-        if (priority < MetricsProviderRegistry.priority) {
-            MetricsProviderRegistry.priority = priority;
-            MetricsProviderRegistry.metricsProvider = metricsProvider;
-        }
-    }
-
-    public static void set(MetricsProvider metricsProvider) {
-        MetricsProviderRegistry.priority = Integer.MAX_VALUE;
-        MetricsProviderRegistry.metricsProvider = metricsProvider;
-    }
-
-    /*
-     * @deprecated Use {@link #dataAccessPointMetrics}.
-     */
-    @Deprecated(forRemoval = true)
-    public static void bindPrometheus(DataAccessPointRegistry dapRegistry) {
-        dataAccessPointMetrics(dapRegistry);
-    }
-
-    /** Bind each data access point in a DataAccessPointRegistry to the system 
Micrometer {@link MeterRegistry}. */
-    public static void dataAccessPointMetrics(DataAccessPointRegistry 
dapRegistry) {
-        try {
-            MeterRegistry meterRegistry = 
MetricsProviderRegistry.get().getMeterRegistry();
-            if (meterRegistry != null) {
-                dapRegistry.accessPoints().forEach(dap->{
-                    new FusekiRequestsMetrics( dap ).bindTo( meterRegistry );
-                });
-            }
-        } catch (Throwable th) {
-            Fuseki.configLog.error("Failed to bind all data access points to 
Prometheus", th);
-        }
-    }
-}
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/PrometheusMetricsProvider.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/PrometheusMetricsProvider.java
similarity index 93%
rename from 
jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/PrometheusMetricsProvider.java
rename to 
jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/PrometheusMetricsProvider.java
index c2cd09e4f2..fd55b74cbc 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/PrometheusMetricsProvider.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/PrometheusMetricsProvider.java
@@ -15,14 +15,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.jena.fuseki.metrics.prometheus;
+package org.apache.jena.fuseki.metrics;
 
 import io.micrometer.core.instrument.MeterRegistry;
 import io.micrometer.prometheusmetrics.PrometheusConfig;
 import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;
 import jakarta.servlet.ServletOutputStream;
-import org.apache.jena.fuseki.metrics.FusekiMetrics;
-import org.apache.jena.fuseki.metrics.MetricsProvider;
 import org.apache.jena.fuseki.servlets.HttpAction;
 import org.apache.jena.fuseki.servlets.ServletOps;
 import org.apache.jena.riot.WebContent;
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/SimpleMetricsProvider.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/SimpleMetricsProvider.java
index 6cd62f0699..047ceb6510 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/SimpleMetricsProvider.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/SimpleMetricsProvider.java
@@ -76,5 +76,4 @@ public class SimpleMetricsProvider implements MetricsProvider 
{
         if ( ! str.endsWith("\n") )
             sbuff.append("\n");
     }
-
 }
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/InitPrometheus.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/InitPrometheus.java
deleted file mode 100644
index 8da041724b..0000000000
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/InitPrometheus.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jena.fuseki.metrics.prometheus;
-
-import org.apache.jena.fuseki.metrics.MetricsProviderRegistry;
-import org.apache.jena.sys.JenaSubsystemLifecycle;
-
-public class InitPrometheus implements JenaSubsystemLifecycle {
-
-    @Override
-    public void start() {
-        MetricsProviderRegistry.set(new PrometheusMetricsProvider());
-    }
-
-    @Override
-    public void stop() {
-    }
-
-    @Override
-    public int level() {
-        return 500;
-    }
-}
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/DataUploader.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/DataUploader.java
index df6ae9f57e..33603fa984 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/DataUploader.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/DataUploader.java
@@ -105,7 +105,7 @@ public class DataUploader {
 
     // Previously, Jena has used HttpServletRequests.getParts.
     // Each application server (Tomcat and Jetty) has special configuration.
-    // Use Apache Commons FileUpload as the mulipart parser as it is portable.
+    // Use Apache Commons FileUpload as the multipart parser as it is portable.
 
     /**
      * Process an HTTP upload of RDF files (triples or quads) with content type
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/UploadDetailsWithName.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/UploadDetailsWithName.java
deleted file mode 100644
index 3d60f77a27..0000000000
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/UploadDetailsWithName.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.jena.fuseki.system;
-
-import org.apache.jena.sparql.core.DatasetGraph;
-
-public class UploadDetailsWithName {
-    public final String graphName;
-    public final DatasetGraph data;
-    public final long count;
-    public UploadDetailsWithName(String gn, DatasetGraph dsg, long 
parserCount) {
-        this.graphName = gn;
-        this.data = dsg;
-        this.count = parserCount;
-    }
-}
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/spot/SpotTDB2.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/spot/SpotTDB2.java
index 9e52eb1907..9fc4e416f6 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/spot/SpotTDB2.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/spot/SpotTDB2.java
@@ -172,7 +172,6 @@ class SpotTDB2 {
         /*
          * prefixes...
          */
-        // XXX validateBPT(location, params.getPrefixTableBaseName());
         validateBPT(location, params.getPrefixTableBaseName());
         validateDAT(location, params.getPrefixTableBaseName()+"-data");
 
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle
 
b/jena-fuseki2/jena-fuseki-core/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle
deleted file mode 100644
index ea05cfe552..0000000000
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.jena.fuseki.metrics.prometheus.InitPrometheus
\ No newline at end of file
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
index 313540ee0e..ac7905a216 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
@@ -52,7 +52,8 @@ import org.apache.jena.fuseki.build.FusekiConfig;
 import org.apache.jena.fuseki.ctl.*;
 import org.apache.jena.fuseki.main.cmds.FusekiMain;
 import org.apache.jena.fuseki.main.sys.*;
-import org.apache.jena.fuseki.metrics.MetricsProviderRegistry;
+import org.apache.jena.fuseki.metrics.MetricsProvider;
+import org.apache.jena.fuseki.mod.prometheus.PrometheusMetricsProvider;
 import org.apache.jena.fuseki.server.*;
 import org.apache.jena.fuseki.servlets.*;
 import org.apache.jena.graph.Graph;
@@ -1406,17 +1407,19 @@ public class FusekiServer {
             // FusekiModule call - inspect the DataAccessPointRegistry.
             FusekiModuleStep.configured(modules, this, dapRegistry, 
configModel);
 
-            // Setup Prometheus metrics. This will become a module.
-            bindPrometheus(dapRegistry);
-
-            // Process the DataAccessPointRegistry for security.
-            buildSecurity(dapRegistry);
-
             try {
                 validate();
 
+                // Process the DataAccessPointRegistry for security.
+                buildSecurity(dapRegistry);
+
                 // Build the ServletContextHandler - the Jetty server 
configuration.
                 ServletContextHandler handler = buildFusekiServerContext();
+                handler.getServletContext();
+
+                // Setup Prometheus metrics.
+                bindPrometheus(handler.getServletContext(), dapRegistry);
+
                 boolean hasFusekiSecurityHandler = 
applySecurityHandler(handler);
                 // Prepare the DataAccessPointRegistry.
                 // Put it in the servlet context.
@@ -1427,8 +1430,6 @@ public class FusekiServer {
                 if ( hasFusekiSecurityHandler )
                     applyAccessControl(handler, dapRegistry);
 
-
-
                 if ( jettyServerConfig != null ) {
                     // Jetty server configuration provided.
                     Server server = jettyServer(handler, jettyServerConfig);
@@ -1487,9 +1488,15 @@ public class FusekiServer {
             return dapRegistry;
         }
 
-        private void bindPrometheus(DataAccessPointRegistry dapRegistry) {
-            if ( withMetrics )
-                MetricsProviderRegistry.dataAccessPointMetrics(dapRegistry);
+        private void bindPrometheus(ServletContext servletContext, 
DataAccessPointRegistry dapRegistry) {
+            if ( withMetrics ) {
+                MetricsProvider metricProvider = 
MetricsProvider.getMetricsProvider(servletContext);
+                if ( metricProvider == null ) {
+                    metricProvider = new PrometheusMetricsProvider();
+                    MetricsProvider.setMetricsProvider(servletContext, 
metricProvider);
+                }
+                metricProvider.dataAccessPointMetrics(metricProvider, 
dapRegistry);
+            }
         }
 
         /**
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
index a7a1789c44..bfcf5efda1 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
@@ -38,7 +38,6 @@ import org.apache.jena.fuseki.Fuseki;
 import org.apache.jena.fuseki.FusekiException;
 import org.apache.jena.fuseki.main.FusekiMainInfo;
 import org.apache.jena.fuseki.main.FusekiServer;
-import org.apache.jena.fuseki.main.sys.FusekiAutoModules;
 import org.apache.jena.fuseki.main.sys.FusekiModules;
 import org.apache.jena.fuseki.main.sys.FusekiServerArgsCustomiser;
 import org.apache.jena.fuseki.main.sys.InitFusekiMain;
@@ -333,7 +332,11 @@ public class FusekiMain extends CmdARQ {
 
     private void processStdArguments(Logger log) {
 
-        // ---- Definition type
+        // ---- Command line definition of setup
+        // One dataset
+        // or a config file
+        // or a "standard setup" e.g.SPARQLer
+        // or empty allowed
         int numDefinitions = 0;
         SetupType setup = UNSET;
 
@@ -572,22 +575,12 @@ public class FusekiMain extends CmdARQ {
             serverArgs.jettyConfigFile = jettyConfigFile;
         }
 
-        boolean withModules = hasValueOfTrue(argEnableModules);
-        if ( withModules ) {
-            // Use the discovered ones.
-            FusekiAutoModules.enable(true);
+        if ( serverArgs.fusekiModules == null ) {
             // Allows for external setting of serverArgs.fusekiModules
-            if ( serverArgs.fusekiModules == null ) {
-                FusekiAutoModules.setup();
-                serverArgs.fusekiModules = FusekiModules.getSystemModules();
-            }
-        } else {
-            // Disabled module discovery.
-            FusekiAutoModules.enable(false);
-            // Allows for external setting of serverArgs.fusekiModules
-            if ( serverArgs.fusekiModules == null ) {
-                serverArgs.fusekiModules = FusekiModules.empty();
-            }
+            boolean withModules = hasValueOfTrue(argEnableModules);
+            serverArgs.fusekiModules = withModules
+                    ? FusekiModules.getSystemModules()
+                    : FusekiModules.empty();
         }
 
         if ( contains(argCORS) ) {
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java
index 2c61c0ed09..72c121a1c3 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.fuseki.main.cmds;
 
-import org.apache.jena.fuseki.run.FusekiModServer;
+import org.apache.jena.fuseki.mod.FusekiModServer;
 import org.apache.jena.fuseki.system.FusekiLogging;
 
 /** Fuseki command that runs a Fuseki server with the admin UI.
@@ -45,9 +45,6 @@ public class FusekiServerCmd {
      * syntax but not start it.
      */
     static public void main(String... args) {
-        // Fix up args
-        // --empty
-        // --modules=true
         FusekiModServer.runAsync(args).join();
     }
 }
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiAutoModules.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiAutoModules.java
index 5c94e9841b..345755782b 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiAutoModules.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiAutoModules.java
@@ -23,12 +23,11 @@ import java.util.Objects;
 import java.util.ServiceConfigurationError;
 import java.util.ServiceLoader;
 import java.util.function.Function;
-import org.apache.jena.atlas.lib.Lib;
+
 import org.apache.jena.atlas.lib.Version;
 import org.apache.jena.atlas.logging.FmtLog;
 import org.apache.jena.fuseki.Fuseki;
 import org.apache.jena.fuseki.FusekiConfigException;
-import org.apache.jena.fuseki.main.FusekiServer;
 import org.slf4j.Logger;
 
 /**
@@ -39,54 +38,19 @@ public class FusekiAutoModules {
     private static final Logger LOG = Fuseki.serverLog;
     private static final Object lock = new Object();
 
-    public static final String logLoadingProperty = "fuseki.logLoading";
-    public static final String envLogLoadingProperty = "FUSEKI_LOGLOADING";
-
-    private static boolean allowDiscovery = true;
-    private static boolean enabled = true;
-
-    /*package*/ static boolean logModuleLoading() {
-        return Lib.isPropertyOrEnvVarSetToTrue(logLoadingProperty, 
envLogLoadingProperty);
-    }
-
-    /*package*/ static Logger logger() {
-        return LOG;
-    }
-
-    /**
-     * Enable/disable discovery of modules using the service loader.
-     * The default is 'enabled'.
-     */
-    public static void enable(boolean setting) {
-        enabled = setting;
-    }
-
-    /** Whether the system loaded modules are enabled. */
-    public static boolean isEnabled() {
-        return enabled;
-    }
-
-    /**
-     * Setup discovery of modules using the service loader.
-     * This replaces any current setup.
-     */
-    public static void setup() {
-        if ( ! isEnabled() )
-            return;
-        // Setup and discover now
-        autoModules = createServiceLoaderModules();
-    }
-
+    // Remember last loading.
     private static FusekiModules currentLoadedModules = null;
+    // ServiceLoader
+    private static ServiceLoader<FusekiAutoModule> serviceLoader = null;
+
 
     /**
      * Load FusekiAutoModules. This call reloads the modules every call.
-     * If disabled, return an empty {@link FusekiModules}.
      */
     static FusekiModules load() {
-        if ( ! enabled )
-            return FusekiModules.empty();
-        currentLoadedModules = getServiceLoaderModules().load();
+        if ( serviceLoader == null )
+            serviceLoader = createServiceLoader();
+        currentLoadedModules = loadAutoModules(serviceLoader);
         return get();
     }
 
@@ -101,131 +65,79 @@ public class FusekiAutoModules {
 
     // -- ServiceLoader machinery.
 
-    // testing
-    /*package*/ static void reset() {
-        load();
-    }
-
-    // Single auto-module controller.
-    private static FusekiServiceLoaderModules autoModules = null;
-
-    private static FusekiServiceLoaderModules getServiceLoaderModules() {
-        // Load once.
-        if ( autoModules == null )
-            setup();
-        return autoModules;
-    }
-
-    private static FusekiServiceLoaderModules createServiceLoaderModules() {
-        FusekiServiceLoaderModules newAutoModules = new 
FusekiServiceLoaderModules();
-        newAutoModules.setDiscovery();
-        return newAutoModules;
-    }
+//    // testing
+//    /*package*/ static void reset() {
+//        load();
+//    }
 
     /**
-     * Use {@link java.util.ServiceLoader} to find {@link FusekiModule}
-     * available via the classpath or modules.
-     * <p>
-     * These are the modules used when building a {@link FusekiServer} if
-     * {@link FusekiServer.Builder#setFusekiModules} is not used.
+     * Discover FusekiModules via {@link java.util.ServiceLoader}.
+     * This step does not create the module objects.
      */
-    private static class FusekiServiceLoaderModules {
-
-        // This keeps the list of discovered Fuseki modules.
-        private ServiceLoader<FusekiAutoModule> serviceLoader = null;
-
-        private FusekiServiceLoaderModules() { }
-
-        private void setDiscovery() {
-            serviceLoader = discover();
-        }
-
-        /**
-         * Discover FusekiModules via {@link java.util.ServiceLoader}.
-         * This step does not create the module objects.
-         */
-        private ServiceLoader<FusekiAutoModule> discover() {
-            // Look for the 4.8.0 name (FusekiModule) which (4.9.0) is split 
into
-            // FusekiModule (interface) and FusekiAutoModule (this is loaded 
by ServiceLoader)
-            // Remove sometime!
-            discoveryWarnLegacy();
-
-            Class<FusekiAutoModule> moduleClass = FusekiAutoModule.class;
-            ServiceLoader<FusekiAutoModule> newServiceLoader = null;
-            synchronized (this) {
-                try {
-                    newServiceLoader = ServiceLoader.load(moduleClass, 
this.getClass().getClassLoader());
-                } catch (ServiceConfigurationError ex) {
-                    FmtLog.error(LOG, ex, "Problem with service loading for 
%s", moduleClass.getName());
-                    throw ex;
-                }
-                if ( LOG.isDebugEnabled() ) {
-                    newServiceLoader.stream().forEach(provider->{
-                        FmtLog.info(LOG, "Fuseki Module: %s", 
provider.type().getSimpleName());
-                    });
-                }
-            }
-            return newServiceLoader;
-        }
-
-        private void discoveryWarnLegacy() {
-            Class<FusekiModule> moduleClass = FusekiModule.class;
+    private static ServiceLoader<FusekiAutoModule> createServiceLoader() {
+        Class<FusekiAutoModule> moduleClass = FusekiAutoModule.class;
+        ServiceLoader<FusekiAutoModule> newServiceLoader = null;
+        synchronized (lock) {
             try {
-                ServiceLoader<FusekiModule> newServiceLoader = 
ServiceLoader.load(moduleClass, this.getClass().getClassLoader());
+                newServiceLoader = ServiceLoader.load(moduleClass, 
FusekiAutoModules.class.getClassLoader());
+            } catch (ServiceConfigurationError ex) {
+                FmtLog.error(LOG, ex, "Problem with service loading for %s", 
moduleClass.getName());
+                throw ex;
+            }
+            if ( LOG.isDebugEnabled() ) {
                 newServiceLoader.stream().forEach(provider->{
-                    FmtLog.warn(FusekiAutoModules.class, "Ignored: \"%s\" : 
legacy use of interface FusekiModule which has changed to FusekiAutoModule", 
provider.type().getSimpleName());
+                    FmtLog.info(LOG, "Fuseki Module: %s", 
provider.type().getSimpleName());
                 });
-            } catch (ServiceConfigurationError ex) {
-                // Ignore - we were only checking.
             }
         }
+        return newServiceLoader;
+    }
 
-        /**
-         * Instantiate modules found using the ServiceLoader.
-         * Each call to {@code load()} creates a new object for the 
FusekiModule.
-         * {@code start()} on each module has not been called.
-         */
-        private FusekiModules load() {
-            if ( serviceLoader == null ) {
-                FmtLog.error(LOG, "Discovery step has not happened or it 
failed. Call FusekiSystemModules.discovery before FusekiSystemModules.load()");
-                throw new FusekiConfigException("Discovery not performed");
-            }
-
-            Function<ServiceLoader.Provider<FusekiAutoModule>, 
FusekiAutoModule> mapper = provider -> {
-                try {
-                    FusekiAutoModule afmod =  provider.get();
-                    return afmod;
-                } catch (ServiceConfigurationError ex) {
-                    FmtLog.error(LOG, ex,
-                                 "Error instantiating class %s for %s", 
provider.type().getName(), FusekiModule.class.getName());
-                    return null;
-                }
-            };
-
-            // Create auto-module object, skip loads in error, sort 
auto-modules into level order.
-            List<FusekiAutoModule> autoMods = serviceLoader.stream()
-                    .map(mapper)
-                    .filter(Objects::nonNull)
-                    .sorted((x,y)-> Integer.compare(x.level(), y.level()))
-                    .toList();
-            // Start, and convert to FusekiModules (generics issue)
-            List<FusekiModule> fmods = autoMods.stream().map(afmod->{
-                afmod.start();
-                return (FusekiModule)afmod;
-            }).toList();
-
-            fmods.forEach(m->{
-                String name = m.name();
-                if ( name == null )
-                    name = m.getClass().getSimpleName();
-                String verStr = 
Version.versionForClass(m.getClass()).orElse(null);
-                if ( verStr == null )
-                    FmtLog.info(LOG, "Module: %s", name);
-                else
-                    FmtLog.info(LOG, "Module: %s (%s)", name, verStr);
-            });
-
-            return FusekiModules.create(fmods);
+    /**
+     * Instantiate modules found using a ServiceLoader.
+     * Each call to {@code load()} creates a new object for the FusekiModule.
+     * {@code start()} on each module has not been called.
+     */
+    private static FusekiModules 
loadAutoModules(ServiceLoader<FusekiAutoModule> serviceLoader) {
+        if ( serviceLoader == null ) {
+            FmtLog.error(LOG, "Discovery step has not happened or it failed. 
Call FusekiSystemModules.discovery before FusekiSystemModules.load()");
+            throw new FusekiConfigException("Discovery not performed");
         }
+
+        Function<ServiceLoader.Provider<FusekiAutoModule>, FusekiAutoModule> 
mapper = provider -> {
+            try {
+                FusekiAutoModule afmod =  provider.get();
+                return afmod;
+            } catch (ServiceConfigurationError ex) {
+                FmtLog.error(LOG, ex,
+                             "Error instantiating class %s for %s", 
provider.type().getName(), FusekiModule.class.getName());
+                return null;
+            }
+        };
+
+        // Create auto-module object, skip loads in error, sort auto-modules 
into level order.
+        List<FusekiAutoModule> autoMods = serviceLoader.stream()
+                .map(mapper)
+                .filter(Objects::nonNull)
+                .sorted((x,y)-> Integer.compare(x.level(), y.level()))
+                .toList();
+        // Start, and convert to FusekiModules (generics issue)
+        List<FusekiModule> fmods = autoMods.stream().map(afmod->{
+            afmod.start();
+            return (FusekiModule)afmod;
+        }).toList();
+
+        fmods.forEach(m->{
+            String name = m.name();
+            if ( name == null )
+                name = m.getClass().getSimpleName();
+            String verStr = Version.versionForClass(m.getClass()).orElse(null);
+            if ( verStr == null )
+                FmtLog.info(LOG, "Module: %s", name);
+            else
+                FmtLog.info(LOG, "Module: %s (%s)", name, verStr);
+        });
+
+        return FusekiModules.create(fmods);
     }
 }
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java
index 8075d644d2..5d0d524de8 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java
@@ -50,6 +50,11 @@ public class FusekiModules {
     }
 
     public static FusekiModules getSystemModules() {
+        if ( systemFusekiModules == null ) {
+            if ( autoLoadedFusekiModules == null )
+                autoLoadedFusekiModules = FusekiAutoModules.get();
+            systemFusekiModules = autoLoadedFusekiModules;
+        }
         return systemFusekiModules;
     }
 
@@ -78,10 +83,6 @@ public class FusekiModules {
         return new FusekiModules(modules);
     }
 
-//    public static FusekiModules autoloadedModules() {
-//        return FusekiAutoModules.load();
-//    }
-
     private final List<FusekiModule> modules;
 
     private FusekiModules(FusekiModule ... modules) {
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java
index 1e52a65b69..f7d0fc446a 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java
@@ -66,11 +66,11 @@ public class ActionBackupList extends ActionCtl {
     };
 
     private JsonValue description(HttpAction action) {
-        if ( ! Files.isDirectory(FusekiApp.dirBackups) )
-            ServletOps.errorOccurred(format("[%d] Backup area '%s' is not a 
directory", action.id, FusekiApp.dirBackups));
+        if ( ! Files.isDirectory(FusekiServerCtl.dirBackups) )
+            ServletOps.errorOccurred(format("[%d] Backup area '%s' is not a 
directory", action.id, FusekiServerCtl.dirBackups));
 
         List<Path> paths = new ArrayList<>();
-        try (DirectoryStream<Path> stream = 
Files.newDirectoryStream(FusekiApp.dirBackups, filterVisibleFiles)) {
+        try (DirectoryStream<Path> stream = 
Files.newDirectoryStream(FusekiServerCtl.dirBackups, filterVisibleFiles)) {
             stream.forEach(paths::add);
         } catch (IOException ex) {
             action.log.error(format("[%d] Backup file list :: IOException :: 
%s", action.id, ex.getMessage()));
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
index 7630b8c6f0..977bcfea69 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
@@ -140,7 +140,7 @@ public class ActionDatasets extends ActionContainerItem {
                 // ----
                 // Keep a persistent copy immediately.  This is not used for
                 // anything other than being "for the record".
-                systemFileCopy = 
FusekiApp.dirSystemFileArea.resolve(uuid.toString()).toString();
+                systemFileCopy = 
FusekiServerCtl.dirSystemFileArea.resolve(uuid.toString()).toString();
                 try ( OutputStream outCopy = IO.openOutputFile(systemFileCopy) 
) {
                     RDFDataMgr.write(outCopy, descriptionModel, Lang.TURTLE);
                 }
@@ -186,8 +186,8 @@ public class ActionDatasets extends ActionContainerItem {
 
                 action.log.info(format("[%d] Create database : name = %s", 
action.id, datasetPath));
 
-                configFile = 
FusekiApp.generateConfigurationFilename(datasetPath);
-                List<String> existing = 
FusekiApp.existingConfigurationFile(datasetPath);
+                configFile = 
FusekiServerCtl.generateConfigurationFilename(datasetPath);
+                List<String> existing = 
FusekiServerCtl.existingConfigurationFile(datasetPath);
                 if ( ! existing.isEmpty() )
                     ServletOps.error(HttpSC.CONFLICT_409, "Configuration file 
for '"+datasetPath+"' already exists");
 
@@ -318,7 +318,7 @@ public class ActionDatasets extends ActionContainerItem {
 
                 // Find the configuration.
                 String filename = name.startsWith("/") ? name.substring(1) : 
name;
-                List<String> configurationFiles = 
FusekiApp.existingConfigurationFile(filename);
+                List<String> configurationFiles = 
FusekiServerCtl.existingConfigurationFile(filename);
 
                 if ( configurationFiles.isEmpty() ) {
                     // ---- Unmanaged
@@ -352,7 +352,7 @@ public class ActionDatasets extends ActionContainerItem {
                 boolean isTDB1 = 
org.apache.jena.tdb1.sys.TDBInternal.isTDB1(dataService.getDataset());
                 boolean isTDB2 = 
org.apache.jena.tdb2.sys.TDBInternal.isTDB2(dataService.getDataset());
 
-                // TODO This occasionally fails in tests due to outstanding 
transactions.
+                // This occasionally fails in tests due to outstanding 
transactions.
                 try {
                     dataService.shutdown();
                 } catch (JenaException ex) {
@@ -363,7 +363,7 @@ public class ActionDatasets extends ActionContainerItem {
                     // Delete databases created by the UI, or the admin 
operation, which are
                     // in predictable, unshared location on disk.
                     // There may not be any database files, the in-memory case.
-                    Path pDatabase = FusekiApp.dirDatabases.resolve(filename);
+                    Path pDatabase = 
FusekiServerCtl.dirDatabases.resolve(filename);
                     if ( Files.exists(pDatabase)) {
                         try {
                             if ( Files.isSymbolicLink(pDatabase)) {
@@ -411,7 +411,7 @@ public class ActionDatasets extends ActionContainerItem {
             params.put(Template.NAME, dbName.substring(1));
         else
             params.put(Template.NAME, dbName);
-        FusekiApp.addGlobals(params);
+        FusekiServerCtl.addGlobals(params);
 
         //action.log.info(format("[%d] Create database : name = %s, type = 
%s", action.id, dbName, dbType ));
 
@@ -430,36 +430,6 @@ public class ActionDatasets extends ActionContainerItem {
         DataUploader.incomingData(action, dest);
     }
 
-    // [ADMIN]
-//    // Persistent state change.
-//    private static void setDatasetState(String name, Resource newState) {
-//        boolean committed = false;
-//        system.begin(ReadWrite.WRITE);
-//        try {
-//            String dbName = name;
-//            if ( dbName.startsWith("/") )
-//                dbName = dbName.substring(1);
-//
-//            String update =  StrUtils.strjoinNL
-//                (PREFIXES,
-//                 "DELETE { GRAPH ?g { ?s fu:status ?state } }",
-//                 "INSERT { GRAPH ?g { ?s fu:status 
"+FmtUtils.stringForRDFNode(newState)+" } }",
-//                 "WHERE {",
-//                 "   GRAPH ?g { ?s fu:name '"+dbName+"'; ",
-//                 "                 fu:status ?state .",
-//                 "   }",
-//                 "}"
-//                 );
-//            UpdateRequest req =  UpdateFactory.create(update);
-//            UpdateAction.execute(req, system);
-//            system.commit();
-//            committed = true;
-//        } finally {
-//            if ( ! committed ) system.abort();
-//            system.end();
-//        }
-//    }
-
     // ---- Auxiliary functions
 
     private static Quad getOne(DatasetGraph dsg, Node g, Node s, Node p, Node 
o) {
@@ -482,8 +452,6 @@ public class ActionDatasets extends ActionContainerItem {
         return stmt;
     }
 
-    // TODO Merge with Upload.incomingData
-
     private static void bodyAsGraph(HttpAction action, StreamRDF dest) {
         HttpServletRequest request = action.getRequest();
         String base = ActionLib.wholeRequestURL(request);
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java
index 70ea5f5156..ea661c7ae6 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java
@@ -54,7 +54,7 @@ public class Backup
 
         String timestamp = DateTimeUtils.nowAsString("yyyy-MM-dd_HH-mm-ss");
         String filename = ds + "_" + timestamp;
-        filename = FusekiApp.dirBackups.resolve(filename).toString();
+        filename = FusekiServerCtl.dirBackups.resolve(filename).toString();
         return filename;
     }
 
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiApp.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiServerCtl.java
similarity index 77%
rename from 
jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiApp.java
rename to 
jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiServerCtl.java
index d6cd0dc570..3710157122 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiApp.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiServerCtl.java
@@ -56,24 +56,33 @@ import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.assembler.AssemblerUtils;
 import org.apache.jena.system.G;
 
-public class FusekiApp {
+public class FusekiServerCtl {
+    /**
+     * Root of the varying files in this deployment. Often $PWD/run.
+     * This location must be writable.
+     */
+    public static Path FUSEKI_BASE = null;
+
+    public static final String DFT_SHIRO_INI          = "shiro.ini";
+    public static final String envFusekiBase          = "FUSEKI_BASE";
+    public static final String envFusekiShiro         = "FUSEKI_SHIRO";
+
     // Relative names of directories in the FUSEKI_BASE area.
-    public static final String     databasesLocationBase    = "databases";
+    private static final String databasesLocationBase = "databases";
     // Place to put Lucene text and spatial indexes.
-    //private static final String        databaseIndexesDir       = "indexes";
+    private static final String    databaseIndexesDir = "text_indexes";
 
-    public static final String     backupDirNameBase        = "backups";
-    public static final String     configDirNameBase        = "configuration";
-    public static final String     logsNameBase             = "logs";
-    public static final String     systemFileAreaBase       = "system_files";
-    public static final String     templatesNameBase        = "templates";
-    public static final String     DFT_SHIRO_INI            = "shiro.ini";
-    public static final String     DFT_CONFIG               = "config.ttl";
+    private static final String backupDirNameBase     = "backups";
+    private static final String configDirNameBase     = "configuration";
+    private static final String logsNameBase          = "logs";
+    private static final String systemFileAreaBase    = "system_files";
+    private static final String templatesNameBase     = "templates";
+    private static final String DFT_CONFIG            = "config.ttl";
 
-    private static int BaseFusekiAutoModuleLevel            = 500;
-    public static int levelFModAdmin                        = 
BaseFusekiAutoModuleLevel;
-    public static int levelFModUI                           = 
BaseFusekiAutoModuleLevel+10;
-    public static int levelFModShiro                        = 
BaseFusekiAutoModuleLevel+20;
+    private static int BaseFusekiAutoModuleLevel      = 500;
+    public static int levelFModAdmin                  = 
BaseFusekiAutoModuleLevel;
+    public static int levelFModUI                     = 
BaseFusekiAutoModuleLevel+10;
+    public static int levelFModShiro                  = 
BaseFusekiAutoModuleLevel+20;
 
 
     /** Directory for TDB databases - this is known to the assembler templates 
*/
@@ -88,9 +97,6 @@ public class FusekiApp {
     /** Directory for assembler files */
     public static Path        dirLogs            = null;
 
-//    /** Directory for system database */
-//    public static Path        dirSystemDatabase  = null;
-
     /** Directory for files uploaded (e.g upload assembler descriptions); not 
data uploads. */
     public static Path        dirSystemFileArea  = null;
 
@@ -101,24 +107,51 @@ public class FusekiApp {
     // Marks the end of successful initialization.
     /*package*/static boolean serverInitialized  = false;
 
-
-
-//    /**
+//    /** OLD
 //     * Root of the Fuseki installation for fixed files.
 //     * This may be null (e.g. running inside a web application container)
 //     */
 //    public static Path FUSEKI_HOME = null;
 
-    /**
-     * Root of the varying files in this deployment. Often $PWD/run.
-     * This must be writable.
-     */
-    public static Path FUSEKI_BASE = set_FUSEKI_BASE();
+    // Too much is done with statics, assuming one server+admin process
+    // At the moment, it is one setup happening at a time.
+    // Once created, it is independent.
+    // Better, less statics, more FusekiServerApp instance object.
+    // Current limitation: FUSEKI_BASE is static and set in
+    //    FusekiApp.java          line 275
+    //    ArgModuleAdmin.java     line 61
+    //    FMod_Admin.java         line 117
+    //    FMod_UI.java            line 133
+    //    Template.java           line 27
+    //    ActionDadasets uses addGlobals.
+
+    // Default - "run" in the current directory.
+    public static final String dftFusekiBase = "run";
+
+    public FusekiServerCtl(String location) {
+        if ( location == null ) {
+            FUSEKI_BASE = null;
+            return;
+        }
+        FUSEKI_BASE = Path.of(location);
+    }
 
-    public static String envFusekiBase = "FUSEKI_BASE";
-    public static String envFusekiShiro = "FUSEKI_SHIRO";
+    public Path setup() {
+        // Set the location of the BASE area
+        setFusekiBase();
+        // Ensure the BASE area exists on disk.
+        setBaseAreaOnDisk();
+        // Format the BASE area.
+        ensureBaseArea(FUSEKI_BASE);
+        return FUSEKI_BASE;
+    }
 
-    private static Path set_FUSEKI_BASE() {
+    private void setFusekiBase() {
+        if ( FUSEKI_BASE == null )
+            FUSEKI_BASE = select_FUSEKI_BASE();
+    }
+
+    private Path select_FUSEKI_BASE() {
         // Does not guarantee existence
         Path setting = null;
         if ( FUSEKI_BASE == null )
@@ -127,20 +160,14 @@ public class FusekiApp {
         return setting;
     }
 
-    private static Path calc_FUSEKI_BASE() {
+    private Path calc_FUSEKI_BASE() {
         String valueFusekiBase = getenv("FUSEKI_BASE");
         if ( valueFusekiBase == null )
             valueFusekiBase = dftFusekiBase;
         return Path.of(valueFusekiBase);
     }
 
-    // Default - "run" in the current directory.
-    public static final String dftFusekiBase = "run";
-
-    static void setEnvironment() {
-        if ( FUSEKI_BASE == null )
-            FUSEKI_BASE = set_FUSEKI_BASE();
-
+    private void setBaseAreaOnDisk() {
         FmtLog.info(Fuseki.configLog, "FUSEKI_BASE=%s", FUSEKI_BASE);
         if ( ! Files.exists(FUSEKI_BASE) ) {
             try {
@@ -152,96 +179,44 @@ public class FusekiApp {
         // Further checks in ensureBaseArea
     }
 
-    public static Path setup() {
-        // Command line arguments "--base" ...
-        setEnvironment();
-        // Format the BASE area.
-        FusekiApp.ensureBaseArea(FUSEKI_BASE);
-        return FUSEKI_BASE;
-    }
-
     /**
      * Create directories if found to be missing.
      */
-    public static void ensureBaseArea(Path FUSEKI_BASE) {
-        if ( Files.exists(FUSEKI_BASE) ) {
-            if ( ! Files.isDirectory(FUSEKI_BASE) )
-                throw new FusekiConfigException("FUSEKI_BASE is not a 
directory: "+FUSEKI_BASE);
-            if ( ! Files.isWritable(FUSEKI_BASE) )
-                throw new FusekiConfigException("FUSEKI_BASE is not writable: 
"+FUSEKI_BASE);
+    private void ensureBaseArea(Path baseArea) {
+        if ( Files.exists(baseArea) ) {
+            if ( ! Files.isDirectory(baseArea) )
+                throw new FusekiConfigException("FUSEKI_BASE is not a 
directory: "+baseArea);
+            if ( ! Files.isWritable(baseArea) )
+                throw new FusekiConfigException("FUSEKI_BASE is not writable: 
"+baseArea);
         } else {
-            ensureDir(FUSEKI_BASE);
+            ensureDir(baseArea);
         }
 
         // Ensure FUSEKI_BASE has the assumed directories.
-        dirTemplates        = writeableDirectory(FUSEKI_BASE, 
templatesNameBase);
-        dirDatabases        = writeableDirectory(FUSEKI_BASE, 
databasesLocationBase);
-        dirBackups          = writeableDirectory(FUSEKI_BASE, 
backupDirNameBase);
-        dirConfiguration    = writeableDirectory(FUSEKI_BASE, 
configDirNameBase);
-        dirLogs             = writeableDirectory(FUSEKI_BASE, logsNameBase);
-        dirSystemFileArea   = writeableDirectory(FUSEKI_BASE, 
systemFileAreaBase);
+        dirTemplates        = writeableDirectory(baseArea, templatesNameBase);
+        dirDatabases        = writeableDirectory(baseArea, 
databasesLocationBase);
+        dirBackups          = writeableDirectory(baseArea, backupDirNameBase);
+        dirConfiguration    = writeableDirectory(baseArea, configDirNameBase);
+        dirLogs             = writeableDirectory(baseArea, logsNameBase);
+        dirSystemFileArea   = writeableDirectory(baseArea, systemFileAreaBase);
 
         // ---- Initialize with files.
 
 //        // Copy missing files into FUSEKI_BASE
         // Interacts with FMod_Shiro.
-        if ( Lib.getenv(FusekiApp.envFusekiShiro) == null ) {
-            copyFileIfMissing(null, DFT_SHIRO_INI, FUSEKI_BASE);
-            System.setProperty(FusekiApp.envFusekiShiro, 
FUSEKI_BASE.resolve(DFT_SHIRO_INI).toString());
+        if ( Lib.getenv(FusekiServerCtl.envFusekiShiro) == null ) {
+            copyFileIfMissing(null, DFT_SHIRO_INI, baseArea);
+            System.setProperty(FusekiServerCtl.envFusekiShiro, 
baseArea.resolve(DFT_SHIRO_INI).toString());
         }
 
-        copyFileIfMissing(null, DFT_CONFIG, FUSEKI_BASE);
+        copyFileIfMissing(null, DFT_CONFIG, baseArea);
         for ( String n : Template.templateNames ) {
-            copyFileIfMissing(null, n, FUSEKI_BASE);
+            copyFileIfMissing(null, n, baseArea);
         }
 
         serverInitialized = true;
     }
 
-    /** Copy a file from src to dst under name fn.
-     * If src is null, try as a classpath resource
-     * @param src   Source directory, or null meaning use java resource.
-     * @param fn    File name, a relative path.
-     * @param dst   Destination directory.
-     *
-     */
-    private static void copyFileIfMissing(Path src, String fn, Path dst) {
-        // fn may be a path.
-        Path dstFile = dst.resolve(fn);
-        if ( Files.exists(dstFile) )
-            return;
-        if ( src != null ) {
-            Path srcFile = src.resolve(fn);
-            if ( ! Files.exists(dstFile) )
-                throw new FusekiConfigException("File not found: "+srcFile);
-            try {
-                IOX.safeWrite(dstFile, output->Files.copy(srcFile, output));
-            } catch (RuntimeIOException e) {
-                throw new FusekiConfigException("Failed to copy file 
"+srcFile+" to "+dstFile, e);
-            }
-        } else {
-            copyFileFromResource(fn, dstFile);
-        }
-    }
-
-    private static void copyFileFromResource(String fn, Path dstFile) {
-        try {
-            // Get from the file from area "org/apache/jena/fuseki/server"
-            String absName = "org/apache/jena/fuseki/server/"+fn;
-            InputStream input = FusekiApp.class
-                    // Else prepends classname as path
-                    .getClassLoader()
-                    .getResourceAsStream(absName);
-
-            if ( input == null )
-                throw new FusekiConfigException("Failed to find resource 
'"+absName+"'");
-            IOX.safeWrite(dstFile, (output)-> input.transferTo(output));
-        }
-        catch (RuntimeException e) {
-            throw new FusekiConfigException("Failed to copy "+fn+" to 
"+dstFile, e);
-        }
-    }
-
     private static List<DataAccessPoint> processServerConfigFile(String 
configFilename) {
         if ( ! FileOps.exists(configFilename) ) {
             Fuseki.configLog.warn("Configuration file '" + configFilename+"' 
does not exist");
@@ -255,7 +230,7 @@ public class FusekiApp {
         return x;
     }
 
-    private static DataAccessPoint configFromTemplate(String templateFile, 
String datasetPath,
+    private DataAccessPoint configFromTemplate(String templateFile, String 
datasetPath,
                                                       boolean allowUpdate, 
Map<String, String> params) {
         // ---- Setup
         if ( params == null ) {
@@ -308,6 +283,50 @@ public class FusekiApp {
 //            params.put("FUSEKI_HOME", 
pathStringOrElse(FusekiAppEnv.FUSEKI_HOME, "unset"));
     }
 
+    /** Copy a file from src to dst under name fn.
+     * If src is null, try as a classpath resource
+     * @param src   Source directory, or null meaning use java resource.
+     * @param fn    File name, a relative path.
+     * @param dst   Destination directory.
+     *
+     */
+    private static void copyFileIfMissing(Path src, String fn, Path dst) {
+        // fn may be a path.
+        Path dstFile = dst.resolve(fn);
+        if ( Files.exists(dstFile) )
+            return;
+        if ( src != null ) {
+            Path srcFile = src.resolve(fn);
+            if ( ! Files.exists(dstFile) )
+                throw new FusekiConfigException("File not found: "+srcFile);
+            try {
+                IOX.safeWrite(dstFile, output->Files.copy(srcFile, output));
+            } catch (RuntimeIOException e) {
+                throw new FusekiConfigException("Failed to copy file 
"+srcFile+" to "+dstFile, e);
+            }
+        } else {
+            copyFileFromResource(fn, dstFile);
+        }
+    }
+
+    private static void copyFileFromResource(String fn, Path dstFile) {
+        try {
+            // Get from the file from area "org/apache/jena/fuseki/server"
+            String absName = "org/apache/jena/fuseki/server/"+fn;
+            InputStream input = FusekiServerCtl.class
+                    // Else prepends classname as path
+                    .getClassLoader()
+                    .getResourceAsStream(absName);
+
+            if ( input == null )
+                throw new FusekiConfigException("Failed to find resource 
'"+absName+"'");
+            IOX.safeWrite(dstFile, (output)-> input.transferTo(output));
+        }
+        catch (RuntimeException e) {
+            throw new FusekiConfigException("Failed to copy "+fn+" to 
"+dstFile, e);
+        }
+    }
+
     private static String pathStringOrElse(Path path, String dft) {
         if ( path == null )
             return dft;
@@ -405,7 +424,7 @@ public class FusekiApp {
         // Without "/"
         if ( filename.startsWith("/"))
             filename = filename.substring(1);
-        Path p = FusekiApp.dirConfiguration.resolve(filename+".ttl");
+        Path p = FusekiServerCtl.dirConfiguration.resolve(filename+".ttl");
         return p.toString();
     }
 
@@ -413,12 +432,12 @@ public class FusekiApp {
     public static List<String> existingConfigurationFile(String baseFilename) {
         try {
             List<String> paths = new ArrayList<>();
-            try (DirectoryStream<Path> stream = 
Files.newDirectoryStream(FusekiApp.dirConfiguration, baseFilename+".*") ) {
-                stream.forEach((p)-> 
paths.add(FusekiApp.dirConfiguration.resolve(p).toString() ));
+            try (DirectoryStream<Path> stream = 
Files.newDirectoryStream(FusekiServerCtl.dirConfiguration, baseFilename+".*") ) 
{
+                stream.forEach((p)-> 
paths.add(FusekiServerCtl.dirConfiguration.resolve(p).toString() ));
             }
             return paths;
         } catch (IOException ex) {
-            throw new InternalErrorException("Failed to read configuration 
directory "+FusekiApp.dirConfiguration);
+            throw new InternalErrorException("Failed to read configuration 
directory "+FusekiServerCtl.dirConfiguration);
         }
     }
 
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java
index 056b161470..c67aa547d1 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java
@@ -20,7 +20,7 @@ package org.apache.jena.fuseki.mgt;
 
 /**
  * Various constants used in the management API functions and JSON responses 
in the
- * webapp/full server.
+ * Fuseki server app.
  */
 public class ServerMgtConst {
     public static final String  opDatasets      = "datasets";
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java
index 2ef63652b8..1266fb3faf 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java
@@ -24,7 +24,7 @@ import java.nio.file.Path;
 public class Template
 {
     public static Path getPath(String templateName) {
-        return FusekiApp.FUSEKI_BASE.resolve(templateName);
+        return FusekiServerCtl.FUSEKI_BASE.resolve(templateName);
     }
 
     public static final String templateDir            = "templates";
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java
new file mode 100644
index 0000000000..99bd550b4f
--- /dev/null
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.mod;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.jena.atlas.lib.FileOps;
+import org.apache.jena.fuseki.main.FusekiServer;
+import org.apache.jena.fuseki.main.cmds.FusekiMain;
+import org.apache.jena.fuseki.main.sys.FusekiModule;
+import org.apache.jena.fuseki.main.sys.FusekiModules;
+import org.apache.jena.fuseki.mod.admin.FMod_Admin;
+import org.apache.jena.fuseki.mod.prometheus.FMod_Prometheus;
+import org.apache.jena.fuseki.mod.shiro.FMod_Shiro;
+import org.apache.jena.fuseki.mod.ui.FMod_UI;
+
+public class FusekiModServer {
+
+    public static void main(String... args) {
+        runAsync(args).join();
+    }
+
+    public static FusekiServer runAsync(String... args) {
+        return construct(args).start();
+    }
+
+    public static FusekiServer construct(String... args) {
+        // Order: FMod_Admin before FMod_Shiro
+        // These modules may have state that is carried across the build steps.
+        FusekiModule fmodShiro = FMod_Shiro.create();
+        FusekiModule fmodAdmin = FMod_Admin.create();
+
+        FusekiModules serverModules = FusekiModules.create( fmodAdmin
+                                                          , FMod_UI.get()
+                                                          , fmodShiro
+                                                          , 
FMod_Prometheus.get() );
+        serverModules.forEach(FusekiMain::addCustomiser);
+
+        System.setProperty("FUSEKI_BASE", "run");
+        FileOps.ensureDir("run");
+
+        // Adjust args.
+        List<String> argList = Arrays.asList(args);
+        // Ensure "--empty", "--modules=true"
+        // Better?: moded startup - i.e. setting defaults.
+
+        if ( args.length == 0 ) {
+            String [] defaultArgs = { "--port=3030", "--empty" };
+            args = defaultArgs;
+        } else {
+            List<String> argsList = new ArrayList<String>(Arrays.asList(args));
+            if ( ! containsArg(argList, "--?empty") )
+                argsList.add(0, "--empty"); // addFirst in java21
+            if ( ! containsArg(argList, "--?modules") )
+                argsList.add(0, "--modules=true");
+            args = argsList.toArray(args);
+        }
+
+        FusekiModules modules = serverModules;
+        FusekiModules.setSystemDefault(modules);
+        FusekiServer server = FusekiServer.construct(args);
+        return server;
+    }
+
+    private static boolean containsArg(List<String> argList, String argRegex) {
+        //Pattern pattern = Pattern.compile(argRegex);
+
+        return argList.stream().anyMatch(arg->{
+            return arg.matches(argRegex);
+        });
+    }
+}
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java
index 6cb3ed0b4e..f1b9d6c751 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java
@@ -26,7 +26,7 @@ import org.apache.jena.cmd.ArgModuleGeneral;
 import org.apache.jena.cmd.CmdArgModule;
 import org.apache.jena.cmd.CmdGeneral;
 import org.apache.jena.fuseki.FusekiConfigException;
-import org.apache.jena.fuseki.mgt.FusekiApp;
+import org.apache.jena.fuseki.mgt.FusekiServerCtl;
 
 public class ArgModuleAdmin implements ArgModuleGeneral {
     // Add a static of "extra command"
@@ -58,7 +58,7 @@ public class ArgModuleAdmin implements ArgModuleGeneral {
         if ( ! Files.isWritable(directory)  )
             throw new FusekiConfigException("Not writable: "+dirStr);
 
-        FusekiApp.FUSEKI_BASE = directory;
+        FusekiServerCtl.FUSEKI_BASE = directory;
     }
 
     @Override
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java
index c48bd290bb..d4ea62e661 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java
@@ -38,7 +38,7 @@ import org.apache.jena.fuseki.main.sys.FusekiModule;
 import org.apache.jena.fuseki.mgt.ActionBackup;
 import org.apache.jena.fuseki.mgt.ActionBackupList;
 import org.apache.jena.fuseki.mgt.ActionDatasets;
-import org.apache.jena.fuseki.mgt.FusekiApp;
+import org.apache.jena.fuseki.mgt.FusekiServerCtl;
 import org.apache.jena.fuseki.server.DataAccessPoint;
 import org.apache.jena.rdf.model.Model;
 import org.slf4j.Logger;
@@ -80,8 +80,8 @@ public class FMod_Admin implements FusekiModule {
         ArgModuleGeneral argModule = new ArgModuleGeneral() {
             @Override
             public void registerWith(CmdGeneral cmdLine) {
-                cmdLine.add(argAdmin, "--admin", "Enable server admin with 
user:password");
-                cmdLine.add(argAdminArea,"--adminRun", "Directory for server 
configuration");
+//                cmdLine.add(argAdmin, "--admin", "Enable server admin with 
user:password");
+//                cmdLine.add(argAdminArea,"--adminRun", "Directory for server 
configuration");
             }
             @Override
             public void processArgs(CmdArgModule cmdLine) {}
@@ -101,6 +101,7 @@ public class FMod_Admin implements FusekiModule {
         if ( dirStr != null )
             directory = Path.of(dirStr);
 
+        // Phase 2
         if ( admin.equals("localhost") ) {}
         else {
             String pwFile = admin;
@@ -113,7 +114,7 @@ public class FMod_Admin implements FusekiModule {
             if ( ! Files.isWritable(directory)  )
                 throw new FusekiConfigException("Not writable: "+dirStr);
         }
-        FusekiApp.FUSEKI_BASE = directory;
+        FusekiServerCtl.FUSEKI_BASE = directory;
     }
 
 //    @Override
@@ -124,28 +125,26 @@ public class FMod_Admin implements FusekiModule {
 
     @Override
     public void prepare(FusekiServer.Builder builder, Set<String> 
datasetNames, Model configModel) {
-        // Unpack
+        // Ensure the work area is setup
 
-        // XXX Do better!
-        FusekiApp.FUSEKI_BASE = null;
-
-//        FusekiApp fusekiApp = new FusekiApp();
-//        //fusekiApp.init();
-//        String fusekiApp.FUSEKI_BASE
-
-        Path path = FusekiApp.setup();
+        Path path;
+        synchronized(FusekiServerCtl.class) {
+            // Temporary - one at a time because FUSEKI_BASE is static.
+            FusekiServerCtl app = new FusekiServerCtl(null);
+            path = app.setup();
+        }
 
         FmtLog.info(LOG, "Fuseki Admin: %s", path);
 
         // Shiro.
-        Path shiroIni = path.resolve(FusekiApp.DFT_SHIRO_INI);
+        Path shiroIni = path.resolve(FusekiServerCtl.DFT_SHIRO_INI);
         if ( Files.exists(shiroIni) ) {
-            System.setProperty(FusekiApp.envFusekiShiro, shiroIni.toString());
+            System.setProperty(FusekiServerCtl.envFusekiShiro, 
shiroIni.toString());
         } else {
             FmtLog.info(LOG, "No shiro.ini: dir=%s", path);
         }
 
-        String configDir = FusekiApp.dirConfiguration.toString();
+        String configDir = FusekiServerCtl.dirConfiguration.toString();
         List<DataAccessPoint> directoryDatabases = 
FusekiConfig.readConfigurationDirectory(configDir);
 
         if ( directoryDatabases.isEmpty() )
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java
deleted file mode 100644
index 7345d58a49..0000000000
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java
+++ /dev/null
@@ -1,2 +0,0 @@
-package org.apache.jena.fuseki.mod;
-
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java
deleted file mode 100644
index 92164563a6..0000000000
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jena.fuseki.mod.prometheus;
-
-import org.apache.jena.fuseki.ctl.ActionCtl;
-import org.apache.jena.fuseki.metrics.MetricsProviderRegistry;
-import org.apache.jena.fuseki.servlets.ActionLib;
-import org.apache.jena.fuseki.servlets.HttpAction;
-import org.apache.jena.fuseki.servlets.ServletOps;
-
-public class ActionMetrics extends ActionCtl {
-
-    public ActionMetrics() { super(); }
-
-    @Override
-    public void execGet(HttpAction action) {
-        super.executeLifecycle(action);
-    }
-
-    @Override
-    public void execOptions(HttpAction action) {
-        ActionLib.doOptionsGet(action);
-        ServletOps.success(action);
-    }
-
-    @Override
-    public void validate(HttpAction action) {}
-
-    @Override
-    public void execute(HttpAction action) {
-        MetricsProviderRegistry.get().scrape( action );
-        ServletOps.success(action);
-    }
-}
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java
index 0d51e0bd4f..c61a45fc05 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java
@@ -20,9 +20,11 @@ package org.apache.jena.fuseki.mod.prometheus;
 
 import java.util.Set;
 
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.ctl.ActionMetrics;
 import org.apache.jena.fuseki.main.FusekiServer;
 import org.apache.jena.fuseki.main.sys.FusekiModule;
-import org.apache.jena.fuseki.metrics.MetricsProviderRegistry;
+import org.apache.jena.fuseki.metrics.MetricsProvider;
 import org.apache.jena.rdf.model.Model;
 
 /**
@@ -42,22 +44,19 @@ public class FMod_Prometheus implements FusekiModule {
 //    @Override
 //    public int level() {
 //        return 5000;
-//    }
-//
-//    @Override public void start() {
-//        Fuseki.configLog.info("FMod Prometheus Metrics");
-//        MetricsProviderRegistry.set(new PrometheusMetricsProvider());
 //    }
 
     @Override
     public String name() { return "FMod Prometheus Metrics"; }
 
     @Override public void prepare(FusekiServer.Builder serverBuilder, 
Set<String> datasetNames, Model configModel) {
-        //MetricsProviderRegistry.set(new PrometheusMetricsProvider());
+        MetricsProvider metricsProvider = new PrometheusMetricsProvider();
+        serverBuilder.addServletAttribute(Fuseki.attrMetricsProvider, 
metricsProvider);
         serverBuilder.addServlet("/$/metrics", new ActionMetrics());
     }
 
     @Override public void server(FusekiServer server) {
-        
MetricsProviderRegistry.dataAccessPointMetrics(server.getDataAccessPointRegistry());
+        MetricsProvider metricsProvider = 
MetricsProvider.getMetricsProvider(server.getServletContext());
+        metricsProvider.dataAccessPointMetrics(metricsProvider, 
server.getDataAccessPointRegistry());
     }
 }
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java
index e70a44d58d..ff0acf2fc4 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java
@@ -34,7 +34,7 @@ import org.apache.jena.fuseki.FusekiConfigException;
 import org.apache.jena.fuseki.main.FusekiServer;
 import org.apache.jena.fuseki.main.cmds.ServerArgs;
 import org.apache.jena.fuseki.main.sys.FusekiModule;
-import org.apache.jena.fuseki.mgt.FusekiApp;
+import org.apache.jena.fuseki.mgt.FusekiServerCtl;
 import org.apache.jena.rdf.model.Model;
 import org.apache.shiro.web.servlet.ShiroFilter;
 import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
@@ -45,8 +45,9 @@ import org.slf4j.LoggerFactory;
 /**
  * Fuseki Module for Apache Shiro.
  * <p>
- * TODO
- * Configuration
+ * Looks for an argument {@code --shiro=file}, and
+ * in environment variable {@code FUSEKI_SHIRO}
+ * (including via system proprties).
  */
 public class FMod_Shiro implements FusekiModule {
 
@@ -79,7 +80,6 @@ public class FMod_Shiro implements FusekiModule {
 
     private static ArgDecl argShiroIni = new ArgDecl(true, "shiro", 
"shiro-ini");
 
-    // XXX Should be a per build variable.
     private String shiroFile = null;
 
     public FMod_Shiro() {
@@ -122,7 +122,7 @@ public class FMod_Shiro implements FusekiModule {
     public void prepare(FusekiServer.Builder serverBuilder, Set<String> 
datasetNames, Model configModel) {
         if ( shiroFile == null ) {
             // Environment variable:  FUSEKI_SHIRO
-            shiroFile = Lib.getenv(FusekiApp.envFusekiShiro);
+            shiroFile = Lib.getenv(FusekiServerCtl.envFusekiShiro);
         }
 
         if ( shiroFile == null ) {
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java
index a46ec343aa..51ad0fa32d 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java
@@ -34,7 +34,7 @@ import org.apache.jena.fuseki.FusekiConfigException;
 import org.apache.jena.fuseki.main.FusekiServer;
 import org.apache.jena.fuseki.main.cmds.ServerArgs;
 import org.apache.jena.fuseki.main.sys.FusekiModule;
-import org.apache.jena.fuseki.mgt.FusekiApp;
+import org.apache.jena.fuseki.mgt.FusekiServerCtl;
 import org.apache.jena.fuseki.validation.DataValidator;
 import org.apache.jena.fuseki.validation.IRIValidator;
 import org.apache.jena.fuseki.validation.QueryValidator;
@@ -130,7 +130,7 @@ public class FMod_UI implements FusekiModule {
         // 2:: $FUSEKI_BASE/webapp
         // If the FUSEKI_BASE does not exists, it is created later in 
FMod_admin.prepare
         // and does not include Fuseki app.
-        String x = fromPath(FusekiApp.FUSEKI_BASE, directoryNameUI);
+        String x = fromPath(FusekiServerCtl.FUSEKI_BASE, directoryNameUI);
         if ( x != null ) {
             LOG.info("Fuseki UI - path resource: "+x);
             return x;
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl
 
b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl
index e69de29bb2..ef4f170a30 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl
@@ -0,0 +1,2 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+## Empty file
\ No newline at end of file
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java
index 6b4d86c8fb..8396f202fb 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java
@@ -23,14 +23,12 @@ import org.junit.platform.suite.api.Suite;
 
 import org.apache.jena.fuseki.main.TS_FusekiMain;
 import org.apache.jena.fuseki.main.access.TS_SecurityFuseki;
-import org.apache.jena.fuseki.main.prefixes.TS_PrefixesService;
 import org.apache.jena.fuseki.mod.TS_FusekiMods;
 
 @Suite
 @SelectClasses({
     TS_FusekiMain.class,
     TS_SecurityFuseki.class,
-    TS_FusekiMods.class,
-    TS_PrefixesService.class
+    TS_FusekiMods.class
 })
 public class TC_FusekiServer {}
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
index f2ba8dda13..7371b5e674 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
@@ -21,7 +21,7 @@ package org.apache.jena.fuseki.main;
 import org.junit.platform.suite.api.SelectClasses;
 import org.junit.platform.suite.api.Suite;
 
-import org.apache.jena.fuseki.main.prefixes.TS_PrefixesService;
+import org.apache.jena.fuseki.main.prefixes.PrefixesServiceTests;
 import org.apache.jena.fuseki.main.sys.TestFusekiModules;
 
 @Suite
@@ -54,7 +54,7 @@ import org.apache.jena.fuseki.main.sys.TestFusekiModules;
   , TestPatchFuseki.class
   , TestFusekiCustomScriptFunc.class
 
-  , TS_PrefixesService.class
+  , PrefixesServiceTests.class
   , TestMetrics.class
   , TestFusekiShaclValidation.class
 
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java
index a0e8c17634..1711cb5d5a 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java
@@ -50,7 +50,6 @@ public class TestConfigFile {
 
         """;
 
-
     private static RDFConnection namedServices(String baseURL) {
         return RDFConnectionRemote.newBuilder()
             .destination(baseURL)
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java
index c473c18aaa..bc09f2a130 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java
@@ -18,6 +18,7 @@
 package org.apache.jena.fuseki.main;
 
 import static org.apache.jena.http.HttpLib.handleResponseRtnString;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.InputStream;
@@ -25,11 +26,13 @@ import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandlers;
 
+import org.junit.jupiter.api.Test;
+
 import org.apache.jena.http.HttpEnv;
 import org.apache.jena.http.HttpLib;
 import org.apache.jena.riot.WebContent;
 import org.apache.jena.riot.web.HttpNames;
-import org.junit.jupiter.api.Test;
+import org.apache.jena.web.HttpSC;
 
 public class TestMetrics extends AbstractFusekiTest {
 
@@ -38,8 +41,9 @@ public class TestMetrics extends AbstractFusekiTest {
         String r = serverURL() + "$/metrics";
         HttpRequest request = 
HttpRequest.newBuilder().uri(HttpLib.toRequestURI(r)).build();
         HttpResponse<InputStream> response = 
HttpLib.executeJDK(HttpEnv.getDftHttpClient(), request, 
BodyHandlers.ofInputStream());
-        String body = handleResponseRtnString(response);
+        assertEquals(HttpSC.OK_200, response.statusCode());
 
+        String body = handleResponseRtnString(response);
         String ct = 
response.headers().firstValue(HttpNames.hContentType).orElse(null);
         assertTrue(ct.contains(WebContent.contentTypeTextPlain));
         assertTrue(ct.contains(WebContent.charsetUTF8));
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/TS_PrefixesService.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/PrefixesServiceTests.java
similarity index 96%
rename from 
jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/TS_PrefixesService.java
rename to 
jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/PrefixesServiceTests.java
index 5ad83830a0..815847c62f 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/TS_PrefixesService.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/PrefixesServiceTests.java
@@ -28,6 +28,5 @@ import org.junit.platform.suite.api.Suite;
     , TestPrefixesServiceRDF.class
     , TestPrefixesServicePrefixesMap.class
     , TestPrefixesActionResponse.class
-
 })
-public class TS_PrefixesService {}
+public class PrefixesServiceTests {}
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/LegacyModule.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/LegacyModule.java
deleted file mode 100644
index d0c3404029..0000000000
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/LegacyModule.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.jena.fuseki.main.sys;
-
-/** testing the legacy-discovery code */
-public class LegacyModule implements FusekiModule {
-
-    public LegacyModule() {
-    }
-
-    @Override
-    public String name() {
-        return "LegacyModule";
-    }
-
-}
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleByServiceLoader.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleByServiceLoader.java
index d7ccbfeff1..532ecc62aa 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleByServiceLoader.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleByServiceLoader.java
@@ -26,6 +26,7 @@ import org.apache.jena.fuseki.main.FusekiServer;
 import org.apache.jena.fuseki.server.DataAccessPointRegistry;
 import org.apache.jena.rdf.model.Model;
 
+// Must be in 
jena-fuseki-main/src/test/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiAutoModule
 public class ModuleByServiceLoader extends ModuleForTest implements 
FusekiAutoModule  {
 
     private static ModuleByServiceLoader module = null;
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleForTest.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleForTest.java
index a1b627911d..a87eaf45fa 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleForTest.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleForTest.java
@@ -26,6 +26,7 @@ import org.apache.jena.fuseki.main.FusekiServer;
 import org.apache.jena.fuseki.server.DataAccessPointRegistry;
 import org.apache.jena.rdf.model.Model;
 
+// Not autoloaded
 public class ModuleForTest implements FusekiModule {
 
     public AtomicInteger countPrepared = new AtomicInteger(0);
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java
index fda3ebbd6c..28c020e234 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java
@@ -45,20 +45,13 @@ public class TestFusekiModules {
         // Created, not loaded
     }
 
-    private static void reset() {
-        ModuleByServiceLoader.reset();
-        FusekiModules.resetSystemDefault();
-    }
-
     @Test public void lifecycle_1() {
         ModuleForTest module = new ModuleForTest();
         FusekiModules fmods = FusekiModules.create(module);
-
         // Mock default set.
         FusekiModules.setSystemDefault(fmods);
-
-        FusekiServer.Builder builder = FusekiServer.create().port(0);
         try {
+            FusekiServer.Builder builder = FusekiServer.create().port(0);
             lifecycle(builder, module);
         } finally {
             FusekiModules.setSystemDefault(null);
@@ -66,7 +59,8 @@ public class TestFusekiModules {
     }
 
     @Test public void lifecycle_2() {
-        reset();
+        ModuleByServiceLoader.reset();
+        FusekiModules.resetSystemDefault();
 
         ModuleForTest module = new ModuleForTest();
         FusekiModules fmods = FusekiModules.create(module);
@@ -103,18 +97,24 @@ public class TestFusekiModules {
     }
 
     @Test public void autoload_1() {
-        // Included reload.
+        FusekiModules systemModules = FusekiModules.getSystemModules();
         ModuleByServiceLoader.reset();
-        FusekiModules.resetSystemDefault();
+        try  {
+            FusekiModules loadedModules = FusekiAutoModules.load();
+            FusekiModules.setSystemDefault(loadedModules);
 
-        // Reloaded by FusekiModules.resetSystemDefault
-        assertEquals(1, ModuleByServiceLoader.countLoads.get());
-        assertEquals(1, ModuleByServiceLoader.countStart.get());
+            // Reloaded by FusekiModules.resetSystemDefault
+            assertEquals(1, 
ModuleByServiceLoader.countLoads.get(),"countLoads:");
+            assertEquals(1, ModuleByServiceLoader.countStart.get(), 
"countStart:");
 
-        // Default : loaded FusekiModules
-        FusekiServer.Builder builder = FusekiServer.create().port(0);
-        ModuleForTest module = ModuleByServiceLoader.lastLoaded();
-        lifecycle(builder, module);
+            // Default : loaded FusekiModules
+            FusekiServer.Builder builder = FusekiServer.create().port(0);
+            ModuleForTest module = ModuleByServiceLoader.lastLoaded();
+            lifecycle(builder, module);
+        } finally {
+            ModuleByServiceLoader.reset();
+            FusekiModules.setSystemDefault(systemModules);
+        }
     }
 
     @Test public void server_module_1() {
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java
index 6771fa2f90..f4f261e0e9 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java
@@ -21,13 +21,18 @@ package org.apache.jena.fuseki.mod;
 import org.junit.platform.suite.api.SelectClasses;
 import org.junit.platform.suite.api.Suite;
 
-import org.apache.jena.fuseki.mod.admin.TS_FusekiServerApp;
+import org.apache.jena.fuseki.mod.admin.TestAdmin;
+import org.apache.jena.fuseki.mod.admin.TestFusekiReload;
+import org.apache.jena.fuseki.mod.admin.TestTemplateAddDataset;
 import org.apache.jena.fuseki.mod.metrics.TestModPrometheus;
 import org.apache.jena.fuseki.mod.shiro.TestModShiro;
 
 @Suite
 @SelectClasses({
-    TS_FusekiServerApp.class,
+    // Admin
+    TestAdmin.class,
+    TestFusekiReload.class,
+    TestTemplateAddDataset.class,
     // UI
 
     // Prometheus
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java
index e0a6b2392d..c9d11ed691 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java
@@ -23,7 +23,6 @@ import static 
org.junit.jupiter.api.Assertions.assertNotEquals;
 import org.junit.jupiter.api.Test;
 
 import org.apache.jena.fuseki.main.FusekiServer;
-import org.apache.jena.fuseki.run.FusekiModServer;
 
 /**
  * Test for the whole Fuseki server, not components.
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java
deleted file mode 100644
index b36360b8cb..0000000000
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.jena.fuseki.mod.admin;
-
-import org.junit.platform.suite.api.SelectClasses;
-import org.junit.platform.suite.api.Suite;
-
-@Suite
-@SelectClasses({
-    TestAdmin.class,
-    TestFusekiReload.class,
-    TestTemplateAddDataset.class,
-})
-public class TS_FusekiServerApp {
-
-}
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java
index df0f6bafdd..396b14b289 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java
@@ -56,7 +56,7 @@ import org.apache.jena.fuseki.ctl.ActionSleep;
 import org.apache.jena.fuseki.ctl.JsonConstCtl;
 import org.apache.jena.fuseki.main.FusekiServer;
 import org.apache.jena.fuseki.main.sys.FusekiModules;
-import org.apache.jena.fuseki.mgt.FusekiApp;
+import org.apache.jena.fuseki.mgt.FusekiServerCtl;
 import org.apache.jena.fuseki.mgt.ServerMgtConst;
 import org.apache.jena.fuseki.server.ServerConst;
 import org.apache.jena.fuseki.test.HttpTest;
@@ -121,7 +121,7 @@ public class TestAdmin {
             server.stop();
         serverURL = null;
         // Clearup FMod_Shiro.
-        System.getProperties().remove(FusekiApp.envFusekiShiro);
+        System.getProperties().remove(FusekiServerCtl.envFusekiShiro);
     }
 
     protected String urlRoot() {
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java
index 975876095d..9743802dcf 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java
@@ -36,7 +36,7 @@ import org.apache.jena.atlas.web.TypedInputStream;
 import org.apache.jena.fuseki.Fuseki;
 import org.apache.jena.fuseki.main.FusekiServer;
 import org.apache.jena.fuseki.main.sys.FusekiModules;
-import org.apache.jena.fuseki.mgt.FusekiApp;
+import org.apache.jena.fuseki.mgt.FusekiServerCtl;
 import org.apache.jena.http.HttpOp;
 import org.apache.jena.query.QueryExecution;
 import org.apache.jena.rdfconnection.RDFConnection;
@@ -85,7 +85,7 @@ public class TestTemplateAddDataset {
             server.stop();
         serverURL = null;
         // Clearup FMod_Shiro.
-        System.getProperties().remove(FusekiApp.envFusekiShiro);
+        System.getProperties().remove(FusekiServerCtl.envFusekiShiro);
     }
 
     protected String urlRoot() {
@@ -143,7 +143,7 @@ public class TestTemplateAddDataset {
         int x1 = count(conn);
         assertEquals(1, x1);
 
-        Path pathDB = FusekiApp.dirDatabases.resolve(dbName);
+        Path pathDB = FusekiServerCtl.dirDatabases.resolve(dbName);
 
         if ( hasFiles )
             assertTrue(Files.exists(pathDB));
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java
index ce3be524fb..dbb475b002 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java
@@ -27,13 +27,13 @@ import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandlers;
 
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
 
 import org.apache.jena.fuseki.main.FusekiServer;
 import org.apache.jena.fuseki.main.sys.FusekiModules;
+import org.apache.jena.fuseki.metrics.MetricsProvider;
 import org.apache.jena.fuseki.mod.prometheus.FMod_Prometheus;
+import org.apache.jena.fuseki.mod.prometheus.PrometheusMetricsProvider;
 import org.apache.jena.http.HttpEnv;
 import org.apache.jena.http.HttpLib;
 import org.apache.jena.riot.WebContent;
@@ -41,6 +41,7 @@ import org.apache.jena.riot.web.HttpNames;
 import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.DatasetGraphFactory;
 
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 public class TestModPrometheus {
 
     private FusekiServer testServer = null;
@@ -62,6 +63,15 @@ public class TestModPrometheus {
     }
 
     @Test
+    @Order(1)
+    public void metrics_available() {
+        MetricsProvider metricsProvider = 
MetricsProvider.getMetricsProvider(testServer.getServletContext());
+        assertNotNull(metricsProvider);
+        assertTrue(metricsProvider instanceof PrometheusMetricsProvider);
+    }
+
+    @Test
+    @Order(2)
     public void can_retrieve_metrics() {
         String metricsURL = testServer.serverURL()+"$/metrics";
         HttpRequest request = 
HttpRequest.newBuilder().uri(HttpLib.toRequestURI(metricsURL)).build();
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java
index dafc636f78..48f064f6a7 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java
@@ -37,7 +37,7 @@ import org.apache.jena.fuseki.main.FusekiServer;
 import org.apache.jena.fuseki.main.cmds.FusekiMain;
 import org.apache.jena.fuseki.main.sys.FusekiModule;
 import org.apache.jena.fuseki.main.sys.FusekiModules;
-import org.apache.jena.fuseki.mgt.FusekiApp;
+import org.apache.jena.fuseki.mgt.FusekiServerCtl;
 import org.apache.jena.fuseki.system.FusekiLogging;
 import org.apache.jena.graph.Graph;
 import org.apache.jena.http.HttpEnv;
@@ -62,7 +62,7 @@ public class TestModShiro {
     }
 
     @BeforeEach void before() {
-        System.getProperties().remove(FusekiApp.envFusekiShiro);
+        System.getProperties().remove(FusekiServerCtl.envFusekiShiro);
         AuthEnv.get().clearAuthEnv();
     }
 
@@ -71,7 +71,7 @@ public class TestModShiro {
     }
 
     @AfterAll static void afterAll() {
-        System.getProperties().remove(FusekiApp.envFusekiShiro);
+        System.getProperties().remove(FusekiServerCtl.envFusekiShiro);
     }
 
     private String unlocalhost(FusekiServer server, String dataset) {
@@ -83,7 +83,7 @@ public class TestModShiro {
 
     /** Builder for a server with Shiro */
     private FusekiServer.Builder serverBuilderWithShiro(String filename) {
-        System.getProperties().setProperty(FusekiApp.envFusekiShiro, filename);
+        System.getProperties().setProperty(FusekiServerCtl.envFusekiShiro, 
filename);
         FusekiModules modules = FusekiModules.create(FMod_Shiro.create());
         return FusekiServer.create()
                 .port(0)
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiModule
 
b/jena-fuseki2/jena-fuseki-main/src/test/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiModule
deleted file mode 100644
index fd5ce7ef2e..0000000000
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiModule
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.jena.fuseki.main.sys.LegacyModule
diff --git a/jena-fuseki2/jena-fuseki-server/pom.xml 
b/jena-fuseki2/jena-fuseki-server/pom.xml
index e38fec0b68..28cc33f10e 100644
--- a/jena-fuseki2/jena-fuseki-server/pom.xml
+++ b/jena-fuseki2/jena-fuseki-server/pom.xml
@@ -40,7 +40,13 @@
     <dependency>
       <groupId>org.apache.jena</groupId>
       <artifactId>jena-fuseki-main</artifactId>
-      <version>${project.version}</version>
+      <version>5.3.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-fuseki-ui</artifactId>
+      <version>5.3.0-SNAPSHOT</version>
     </dependency>
 
     <dependency>
@@ -99,7 +105,7 @@
 
           <transformers>
             <transformer 
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-              
<mainClass>org.apache.jena.fuseki.main.cmds.FusekiMainCmd</mainClass>
+              
<mainClass>org.apache.jena.fuseki.main.cmds.FusekiServerCmd</mainClass>
               <!-- https://issues.apache.org/jira/browse/LOG4J2-2537 -->
               <manifestEntries>
                 <Multi-Release>true</Multi-Release>
diff --git 
a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/webapp/FusekiServerListener.java
 
b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/webapp/FusekiServerListener.java
index 469e396f32..50357959a4 100644
--- 
a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/webapp/FusekiServerListener.java
+++ 
b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/webapp/FusekiServerListener.java
@@ -21,11 +21,11 @@ package org.apache.jena.fuseki.webapp;
 import jakarta.servlet.ServletContext;
 import jakarta.servlet.ServletContextEvent;
 import jakarta.servlet.ServletContextListener;
-
 import org.apache.jena.fuseki.Fuseki;
 import org.apache.jena.fuseki.FusekiException;
 import org.apache.jena.fuseki.cmd.FusekiArgs;
-import org.apache.jena.fuseki.metrics.MetricsProviderRegistry;
+import org.apache.jena.fuseki.metrics.MetricsProvider;
+import org.apache.jena.fuseki.metrics.PrometheusMetricsProvider;
 import org.apache.jena.fuseki.server.DataAccessPointRegistry;
 import org.apache.jena.fuseki.server.FusekiCoreInfo;
 import org.apache.jena.fuseki.server.OperationRegistry;
@@ -102,7 +102,9 @@ public class FusekiServerListener implements 
ServletContextListener {
                 //Fuseki.configLog.info("Register: "+dap.getName());
             });
 
-            
MetricsProviderRegistry.dataAccessPointMetrics(dataAccessPointRegistry);
+            MetricsProvider metricsProvider = new PrometheusMetricsProvider();
+            MetricsProvider.setMetricsProvider(servletContext, 
metricsProvider);
+            metricsProvider.dataAccessPointMetrics(metricsProvider, 
dataAccessPointRegistry);
 
         } catch (Throwable th) {
             Fuseki.serverLog.error("Exception in initialization: {}", 
th.getMessage());

Reply via email to