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


The following commit(s) were added to refs/heads/main by this push:
     new c758d2ba90 GH-1804: Configurable fuseki modules
     new 93b80ef0c8 Merge pull request #1805 from afs/fuseki-modules
c758d2ba90 is described below

commit c758d2ba909ad431a4afe972b11ad26782f27566
Author: Andy Seaborne <[email protected]>
AuthorDate: Fri Mar 17 14:14:18 2023 +0000

    GH-1804: Configurable fuseki modules
---
 .../org/apache/jena/fuseki/main/FusekiServer.java  |  58 +++++++++--
 .../jena/fuseki/main/sys/FusekiModuleStep.java     |  29 ++++--
 .../apache/jena/fuseki/main/sys/FusekiModules.java |  80 +++++++--------
 .../jena/fuseki/main/sys/FusekiModulesLoaded.java  |  98 ++++++++++++++++++
 .../jena/fuseki/main/sys/FusekiModulesSystem.java  |  38 +++++++
 .../apache/jena/fuseki/main/sys/InitFuseki.java    |   2 +-
 .../org/apache/jena/fuseki/main/ModuleForTest.java |   9 ++
 .../apache/jena/fuseki/main/TestFusekiModules.java | 112 +++++++++++++++++----
 .../main/examples/ExFusekiMain_3_FusekiModule.java |  17 +++-
 .../ExFuseki_04_CustomOperation_Module.java        |  13 ++-
 10 files changed, 370 insertions(+), 86 deletions(-)

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 59cd6c242e..aaf7ef631f 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
@@ -49,9 +49,7 @@ import org.apache.jena.fuseki.auth.AuthPolicy;
 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.FusekiErrorHandler;
-import org.apache.jena.fuseki.main.sys.FusekiModuleStep;
-import org.apache.jena.fuseki.main.sys.JettyLib;
+import org.apache.jena.fuseki.main.sys.*;
 import org.apache.jena.fuseki.metrics.MetricsProviderRegistry;
 import org.apache.jena.fuseki.server.*;
 import org.apache.jena.fuseki.servlets.*;
@@ -151,15 +149,18 @@ public class FusekiServer {
     private int httpsPort;
     private final String staticContentDir;
     private final ServletContext servletContext;
+    private final FusekiModules modules;
 
     private FusekiServer(int httpPort, int httpsPort, Server server,
                          String staticContentDir,
+                         FusekiModules modules,
                          ServletContext fusekiServletContext) {
-        this.server = server;
+        this.server = Objects.requireNonNull(server);
         this.httpPort = httpPort;
         this.httpsPort = httpsPort;
         this.staticContentDir = staticContentDir;
-        this.servletContext = fusekiServletContext;
+        this.servletContext = Objects.requireNonNull(fusekiServletContext);
+        this.modules = Objects.requireNonNull(modules);
     }
 
     /**
@@ -280,6 +281,13 @@ public class FusekiServer {
         return staticContentDir;
     }
 
+    /**
+     * Return the list of {@link FusekiModule}s for this server.
+     */
+    public FusekiModules getModules() {
+        return modules;
+    }
+
     /**
      * Start the server - the server continues to run after this call returns.
      * To synchronise with the server stopping, call {@link #join}.
@@ -288,7 +296,6 @@ public class FusekiServer {
         try {
             FusekiModuleStep.serverBeforeStarting(this);
             server.start();
-            FusekiModuleStep.serverAfterStarting(this);
         }
         catch (IOException ex) {
             if ( ex.getCause() instanceof 
java.security.UnrecoverableKeyException )
@@ -303,6 +310,7 @@ public class FusekiServer {
             throw new FusekiException(ex);
         }
 
+        // Post-start completion. Find the ports.
         Connector[] connectors = server.getServer().getConnectors();
         if ( connectors.length == 0 )
             serverLog.warn("Start Fuseki: No connectors");
@@ -318,6 +326,8 @@ public class FusekiServer {
             }
         });
 
+        FusekiModuleStep.serverAfterStarting(this);
+
         if ( httpsPort > 0 && httpPort > 0 )
             Fuseki.serverLog.info("Start Fuseki (http="+httpPort+" 
https="+httpsPort+")");
         else if ( httpsPort > 0 )
@@ -415,6 +425,10 @@ public class FusekiServer {
         private List<Pair<String, Filter>> beforeFilters    = new 
ArrayList<>();
         private List<Pair<String, Filter>> afterFilters     = new 
ArrayList<>();
 
+        // Modules to use to process the building of the server.
+        // The default (fusekiModules is null) is the system-wide modules.
+        private FusekiModules            fusekiModules     = null;
+
         private String                   contextPath        = "/";
         private String                   staticContentDir   = null;
         private SecurityHandler          securityHandler    = null;
@@ -1037,6 +1051,26 @@ public class FusekiServer {
             return this;
         }
 
+        /**
+         * Set the {@link FusekiModule Fuseki Module} for a server.
+         * If no modules are added to a builder, then the system-wide default 
set (found by loading FusekiModule
+         * via Java's {@link ServiceLoader} mechanism) is used.
+         * <p>Pass {@code null} to switch back the system-wide default set.
+         *
+         * @see FusekiModules
+         */
+        public Builder setModules(FusekiModules modules) {
+            fusekiModules = modules;
+            return this;
+        }
+
+        /**
+         * Return the current list of Fuseki modules in the builder.
+         */
+        public FusekiModules getFusekiModules() {
+            return fusekiModules;
+        }
+
         /**
          * Add an operation and handler to the server. This does not enable it 
for any dataset.
          * <p>
@@ -1192,9 +1226,13 @@ public class FusekiServer {
             if ( serverHttpPort < 0 && serverHttpsPort < 0 )
                 serverHttpPort = DefaultServerPort;
 
+            FusekiModules modules = (fusekiModules == null)
+                    ? FusekiModulesSystem.get()
+                    : fusekiModules;
+
             // FusekiModule call - final preparations.
             Set<String> datasetNames = Set.copyOf(dataServices.keys());
-            FusekiModuleStep.prepare(this, datasetNames, configModel);
+            FusekiModuleStep.prepare(modules, this, datasetNames, configModel);
 
             // Freeze operation registry (builder may be reused).
             OperationRegistry operationReg = new 
OperationRegistry(operationRegistry);
@@ -1203,7 +1241,7 @@ public class FusekiServer {
             DataAccessPointRegistry dapRegistry = buildStart();
 
             // FusekiModule call - inspect the DataAccessPointRegistry.
-            FusekiModuleStep.configured(this, dapRegistry, configModel);
+            FusekiModuleStep.configured(modules, this, dapRegistry, 
configModel);
 
             // Setup Prometheus metrics. This will become a module.
             bindPrometheus(dapRegistry);
@@ -1228,7 +1266,7 @@ public class FusekiServer {
 
                 if ( jettyServerConfig != null ) {
                     Server server = jettyServer(handler, jettyServerConfig);
-                    return new FusekiServer(-1, -1, server, staticContentDir, 
handler.getServletContext());
+                    return new FusekiServer(-1, -1, server, staticContentDir, 
modules, handler.getServletContext());
                 }
 
                 Server server;
@@ -1245,7 +1283,7 @@ public class FusekiServer {
                 if ( networkLoopback )
                     applyLocalhost(server);
 
-                FusekiServer fusekiServer = new FusekiServer(httpPort, 
httpsPort, server, staticContentDir, handler.getServletContext());
+                FusekiServer fusekiServer = new FusekiServer(httpPort, 
httpsPort, server, staticContentDir, modules, handler.getServletContext());
                 FusekiModuleStep.server(fusekiServer);
                 return fusekiServer;
             } finally {
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModuleStep.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModuleStep.java
index 84223860ed..14623d6c04 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModuleStep.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModuleStep.java
@@ -26,42 +26,55 @@ import org.apache.jena.rdf.model.Model;
 
 /** Call points for FusekiModule extensions */
 public class FusekiModuleStep {
+
     /**
      * Call at the start of "build" step.
      * The builder has been set according to the configuration.
      * The "configModel" parameter is set if a configuration file was used 
else it is null.
      */
+    public static void prepare(FusekiModules modules, FusekiServer.Builder 
serverBuilder, Set<String> datasetNames, Model configModel) {
+        modules.forEach(module -> module.prepare(serverBuilder, datasetNames, 
configModel));
+    }
+
+    /** @deprecated Use {@code prepare(FusekiModules, ...)}. */
+    @Deprecated
     public static void prepare(FusekiServer.Builder serverBuilder, Set<String> 
datasetNames, Model configModel) {
-        FusekiModules.forEachModule(module -> module.prepare(serverBuilder, 
datasetNames, configModel));
+        prepare(systemModules(), serverBuilder, datasetNames, configModel);
     }
 
     /**
      * The DataAccessPointRegistry that will be used to build the server.
      *
      */
+    public static void configured(FusekiModules modules, FusekiServer.Builder 
serverBuilder, DataAccessPointRegistry dapRegistry, Model configModel) {
+        modules.forEach(module -> module.configured(serverBuilder, 
dapRegistry, configModel));
+    }
+
+    /** @deprecated Use {@code configured(FusekiModules.loaded(), ...)}. */
+    @Deprecated
     public static void configured(FusekiServer.Builder serverBuilder, 
DataAccessPointRegistry dapRegistry, Model configModel) {
-        FusekiModules.forEachModule(module -> module.configured(serverBuilder, 
dapRegistry, configModel));
+        configured(systemModules(), serverBuilder, dapRegistry, configModel);
     }
 
     /**
      * The outcome of the "build" step.
      */
     public static void server(FusekiServer server) {
-        FusekiModules.forEachModule(module -> module.server(server));
+        server.getModules().forEach(module -> module.server(server));
     }
 
     /**
      * Called just before {@code server.start()} called.
      */
     public static void serverBeforeStarting(FusekiServer server) {
-        FusekiModules.forEachModule(module -> 
module.serverBeforeStarting(server));
+        server.getModules().forEach(module -> 
module.serverBeforeStarting(server));
     }
 
     /**
      * Called just after {@code server.start()} called.
      */
     public static void serverAfterStarting(FusekiServer server) {
-        FusekiModules.forEachModule(module -> 
module.serverAfterStarting(server));
+        server.getModules().forEach(module -> 
module.serverAfterStarting(server));
     }
 
     /**
@@ -70,6 +83,10 @@ public class FusekiModuleStep {
      * simply exits the JVM or is killed externally.
      */
     public static void serverStopped(FusekiServer server) {
-        FusekiModules.forEachModule(module -> module.serverStopped(server));
+        server.getModules().forEach(module -> module.serverStopped(server));
+    }
+
+    private static FusekiModules systemModules() {
+        return FusekiModulesSystem.get();
     }
 }
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 a1b9bd9093..20c6a100d8 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
@@ -18,67 +18,57 @@
 
 package org.apache.jena.fuseki.main.sys;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 
-import org.apache.jena.base.module.Subsystem;
-
-/** Registry of modules */
+/**
+ * List of {@linkplain FusekiModule Fuseki modules}.
+ * This is the immutable collection of modules for a server.
+ * <p>
+ * @see FusekiModulesLoaded
+ */
 public class FusekiModules {
 
-    private static Object lock = new Object();
+    /** A Fuseki module with no members. */
+    public static final FusekiModules empty = FusekiModules.create();
 
-    // Record of what is loaded.
-    private static List<FusekiModule> registry = null;
-
-    private static Subsystem<FusekiModule> subsystem = null;
+    /** Create a collection of Fuseki modules */
+    public static FusekiModules create(FusekiModule ... modules) {
+        return new FusekiModules(modules);
+    }
 
-    public static void load() {
-        if ( registry == null )
-            reload();
+    /** Create a collection of Fuseki modules */
+    public static FusekiModules create(List<FusekiModule> modules) {
+        return new FusekiModules(modules);
     }
 
-    public static void reload() {
-        registry = new ArrayList<>();
-        subsystem = new Subsystem<FusekiModule>(FusekiModule.class);
-        subsystem.initialize();
-        synchronized(lock) {
-            subsystem.forEach(registry::add);
-        }
+    private final List<FusekiModule> modules;
+
+    private FusekiModules(FusekiModule ... modules) {
+        this.modules = List.of(Objects.requireNonNull(modules));
     }
 
-    /** Add a code module */
-    public static void add(FusekiModule module) {
-        synchronized(lock) {
-            load();
-            module.start();
-            registry.add(module);
-        }
+    private FusekiModules(List<FusekiModule> modules) {
+        this.modules = List.copyOf(Objects.requireNonNull(modules));
     }
 
-    /** Remove a code module */
-    public static void remove(FusekiModule module) {
-        synchronized(lock) {
-            registry.remove(module);
-            module.stop();
-        }
+    /**
+     * Return an immutable list of modules.
+     */
+    public List<FusekiModule> asList() {
+        return List.copyOf(modules);
     }
 
-    /** Test whether a code module is registered. */
-    public static boolean contains(FusekiModule module) {
-        synchronized(lock) {
-            return registry.contains(module);
-        }
+    /**
+     * Apply an action to each module, in order, one at a time.
+     */
+    public void forEach(Consumer<FusekiModule> action) {
+        modules.forEach(action);
     }
 
-    /*package*/ static void forEachModule(Consumer<FusekiModule> action) {
-        synchronized(lock) {
-            if ( registry == null )
-                load();
-            if ( registry == null || registry.isEmpty() )
-                return ;
-            registry.forEach(action);
-        }
+    /** Test whether a code module is registered. */
+    public boolean contains(FusekiModule module) {
+        return modules.contains(module);
     }
 }
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModulesLoaded.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModulesLoaded.java
new file mode 100644
index 0000000000..142eaa6474
--- /dev/null
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModulesLoaded.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.jena.base.module.Subsystem;
+
+/** Control of fuseki modules loaded via ServiceLoader */
+public class FusekiModulesLoaded {
+
+    private static final Object lock = new Object();
+
+    // Record of what is loaded.
+    private static List<FusekiModule> loadedModules = null;
+
+    // Loaded modules as FusekiModules
+    private static FusekiModules loaded = null;
+
+    private static boolean enabled = true;
+
+    /** Enable/disable loaded modules. */
+    public static void enable(boolean setting) {
+        synchronized(lock) {
+            if ( setting ) {
+                if ( ! enabled )
+                    reload();
+            } else {
+                // Clear.
+                loadedModules.forEach(FusekiModule::stop);
+                loadedModules = List.of();
+                loaded = FusekiModules.empty;
+            }
+            enabled = setting;
+        }
+    }
+
+    /* package */ static void init() {
+        load();
+        FusekiModulesSystem.set(loaded);
+    }
+
+    /** Whether the system loaded modules are enabled. */
+    public static boolean isEnabled() {
+        return enabled;
+    }
+
+    /** The system wide list of Fuseki modules. */
+    public static FusekiModules loaded() {
+        return loaded;
+    }
+
+    /** Load the the system wide Fuseki modules if it has not already been 
loaded. */
+    public static void load() {
+        if ( ! enabled )
+            return;
+        if ( loadedModules == null )
+            reload();
+    }
+
+    /**
+     * Load and set system wide Fuseki modules.
+     */
+    public static void reload() {
+        synchronized(lock) {
+            List<FusekiModule> thisLoad = new ArrayList<>();
+            Subsystem<FusekiModule> subsystem = new 
Subsystem<FusekiModule>(FusekiModule.class);
+            subsystem.initialize();
+            subsystem.forEach(thisLoad::add);
+            loadedModules = List.copyOf(thisLoad);
+            loaded = FusekiModules.create(loadedModules);
+            enabled = true;
+        }
+    }
+
+    /** Reload modules and set as the system modules. */
+    public static void resetSystem() {
+        reload();
+        FusekiModulesSystem.set(loaded);
+    }
+}
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModulesSystem.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModulesSystem.java
new file mode 100644
index 0000000000..6959dfc3da
--- /dev/null
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModulesSystem.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import java.util.Objects;
+
+public class FusekiModulesSystem {
+
+    // The immutable system-wide FusekiModules
+    // Used by default during a Fuseki server build cycle.
+    private static FusekiModules systemModules = FusekiModules.empty;
+
+    /** The current system-wide modules. */
+    public static FusekiModules get() {
+        return systemModules;
+    }
+
+    /** Set the system-wide modules. This will then ignore loaded modules. */
+    public static void set(FusekiModules modules) {
+        systemModules = Objects.requireNonNull(modules);
+    }
+}
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/InitFuseki.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/InitFuseki.java
index 88889e6793..80651a26b4 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/InitFuseki.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/InitFuseki.java
@@ -56,7 +56,7 @@ public class InitFuseki implements JenaSubsystemLifecycle {
             }
             initialized = true;
             JenaSystem.logLifecycle("Fuseki.init - start");
-            FusekiModules.load();
+            FusekiModulesLoaded.init();
             try {
                 Cmds.injectCmd("fuseki", a->FusekiMainCmd.main(a));
             } catch (NoClassDefFoundError ex) {
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/ModuleForTest.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/ModuleForTest.java
index c8472de788..8e95bb8c38 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/ModuleForTest.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/ModuleForTest.java
@@ -23,6 +23,7 @@ import java.util.UUID;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.jena.fuseki.main.sys.FusekiModule;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry;
 import org.apache.jena.rdf.model.Model;
 
 public class ModuleForTest implements FusekiModule {
@@ -30,6 +31,7 @@ public class ModuleForTest implements FusekiModule {
     public static ModuleForTest module = null;
 
     public AtomicInteger countStart = new AtomicInteger(0);
+    public AtomicInteger countPrepared = new AtomicInteger(0);
     public AtomicInteger countConfiguration = new AtomicInteger(0);
     public AtomicInteger countServer = new AtomicInteger(0);
     public AtomicInteger countServerBeforeStarting = new AtomicInteger(0);
@@ -49,6 +51,7 @@ public class ModuleForTest implements FusekiModule {
     public void clearLifecycle() {
         // Not countStart.
         countConfiguration.set(0);
+        countPrepared.set(0);
         countServer.set(0);
         countServerBeforeStarting.set(0);
         countServerAfterStarting.set(0);
@@ -61,9 +64,15 @@ public class ModuleForTest implements FusekiModule {
 
     @Override
     public void prepare(FusekiServer.Builder builder, Set<String> 
datasetNames, Model configModel) {
+        countPrepared.incrementAndGet();
+    }
+
+    @Override
+    public void configured(FusekiServer.Builder serverBuilder, 
DataAccessPointRegistry dapRegistry, Model configModel) {
         countConfiguration.getAndIncrement();
     }
 
+
     // Built, not started, about to be returned to the builder caller
     @Override public void server(FusekiServer server) {
         countServer.getAndIncrement();
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiModules.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiModules.java
index 363b47bf7b..0de5a3ecfc 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiModules.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiModules.java
@@ -19,8 +19,17 @@
 package org.apache.jena.fuseki.main;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.jena.fuseki.main.sys.FusekiModule;
 import org.apache.jena.fuseki.main.sys.FusekiModules;
+import org.apache.jena.fuseki.main.sys.FusekiModulesLoaded;
+import org.apache.jena.fuseki.main.sys.FusekiModulesSystem;
+import org.apache.jena.rdf.model.Model;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -32,16 +41,19 @@ public class TestFusekiModules {
     // file :: 
src/test/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiModule
     // Module: ModuleForTest
 
+    private static FusekiModules system = null;
+
     @BeforeClass public static void beforeClass() {
-        FusekiModules.load();
+        system = FusekiModulesSystem.get();
+        FusekiModulesLoaded.resetSystem();
     }
 
     @Before public void beforeTest() {
         ModuleForTest.module.clearLifecycle();
     }
 
-    @AfterClass public static void afterTest() {
-        FusekiModules.remove(ModuleForTest.module);
+    @AfterClass public static void afterClass() {
+        FusekiModulesSystem.set(system);
     }
 
     @Test public void modules_0() {
@@ -51,35 +63,101 @@ public class TestFusekiModules {
         assertEquals(0, module.countConfiguration.get());
     }
 
+    @Test public void modules_1() {
+        boolean bIsEmpty1 = FusekiModulesLoaded.loaded().asList().isEmpty();
+        assertFalse(bIsEmpty1);
+
+        FusekiModulesLoaded.enable(false);
+
+        boolean bIsEmpty2 =
+                FusekiModulesLoaded.loaded().asList().isEmpty();
+        assertTrue(bIsEmpty2);
+
+        FusekiModulesLoaded.enable(true);
+
+        boolean bIsEmpty3 = FusekiModulesLoaded.loaded().asList().isEmpty();
+        assertFalse(bIsEmpty3);
+    }
+
+    @Test public void modules_2() {
+        boolean bIsEmpty1 = FusekiModulesLoaded.loaded().asList().isEmpty();
+        assertFalse(bIsEmpty1);
+        FusekiModulesLoaded.enable(false);
+        boolean bIsEmpty2 =
+                FusekiModulesLoaded.loaded().asList().isEmpty();
+        assertTrue(bIsEmpty2);
+    }
+
+
+
     @Test public void lifecycle_1() {
+        FusekiModulesLoaded.resetSystem();
+
         ModuleForTest module = findModule();
 
+
+        assertFalse(FusekiModulesSystem.get().asList().isEmpty());
+
         FusekiServer.Builder builder = FusekiServer.create().port(0);
 
-        assertEquals(1, module.countStart.get());
-        assertEquals(0, module.countConfiguration.get());
-        assertEquals(0, module.countServer.get());
-        assertEquals(0, module.countServerBeforeStarting.get());
+        assertEquals("start:"  ,       1, module.countStart.get());
+        assertEquals("prepare:",       0, module.countPrepared.get());
+        assertEquals("configured:",    0, module.countConfiguration.get());
+        assertEquals("server: ",       0, module.countServer.get());
+        assertEquals("serverBefore: ", 0, 
module.countServerBeforeStarting.get());
+        assertEquals("serverAfter: ",  0, 
module.countServerAfterStarting.get());
 
         FusekiServer server = builder.build();
 
-        assertEquals(1, module.countStart.get());
-        assertEquals(1, module.countConfiguration.get());
-        assertEquals(1, module.countServer.get());
-        assertEquals(0, module.countServerBeforeStarting.get());
-        assertEquals(0, module.countServerAfterStarting.get());
+
+        assertFalse(server.getModules().asList().isEmpty());
+
+        assertEquals("start:"  ,       1, module.countStart.get());
+        assertEquals("prepare:",       1, module.countPrepared.getPlain());
+        assertEquals("configured:",    1, module.countConfiguration.get());
+        assertEquals("server: ",       1, module.countServer.get());
+        assertEquals("serverBefore: ", 0, 
module.countServerBeforeStarting.get());
+        assertEquals("serverAfter: ",  0, 
module.countServerAfterStarting.get());
 
         server.start();
 
-        assertEquals(1, module.countStart.get());
-        assertEquals(1, module.countConfiguration.get());
-        assertEquals(1, module.countServer.get());
-        assertEquals(1, module.countServerBeforeStarting.get());
-        assertEquals(1, module.countServerAfterStarting.get());
+        assertEquals("start:"  ,       1, module.countStart.get());
+        assertEquals("prepare:",       1, module.countPrepared.get());
+        assertEquals("configured:",    1, module.countConfiguration.get());
+        assertEquals("server: ",       1, module.countServer.get());
+        assertEquals("serverBefore: ", 1, 
module.countServerBeforeStarting.get());
+        assertEquals("serverAfter: ",  1, 
module.countServerAfterStarting.get());
 
         server.stop();
     }
 
+    @Test public void server_module_1() {
+        AtomicBoolean called1 = new AtomicBoolean(false);
+        AtomicBoolean called2 = new AtomicBoolean(false);
+        FusekiModule oneOff = new FusekiModule() {
+            @Override public String name() { return "Local"; }
+            @Override public void prepare(FusekiServer.Builder serverBuilder, 
Set<String> datasetNames, Model configModel) {
+                called1.set(true);
+            }
+            @Override public void serverBeforeStarting(FusekiServer server) {
+                called2.set(true);
+            }
+        };
+
+        assertFalse(FusekiModulesLoaded.loaded().asList().contains(oneOff));
+
+        FusekiModules mods = FusekiModules.create(oneOff);
+        assertFalse(called1.get());
+        assertFalse(called2.get());
+        FusekiServer server = 
FusekiServer.create().port(0).setModules(mods).build();
+        assertTrue(called1.get());
+        assertFalse(called2.get());
+        try {
+            server.start();
+            assertTrue(called2.get());
+        } finally { server.stop(); }
+    }
+
     private ModuleForTest findModule() {
         return ModuleForTest.module;
     }
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/ExFusekiMain_3_FusekiModule.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/ExFusekiMain_3_FusekiModule.java
index bbfbed77e4..737a690d4f 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/ExFusekiMain_3_FusekiModule.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/ExFusekiMain_3_FusekiModule.java
@@ -24,8 +24,9 @@ import java.net.http.HttpRequest;
 import java.net.http.HttpRequest.BodyPublishers;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandlers;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
-import java.util.UUID;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -36,6 +37,7 @@ import org.apache.jena.atlas.io.IO;
 import org.apache.jena.fuseki.main.FusekiServer;
 import org.apache.jena.fuseki.main.sys.FusekiModule;
 import org.apache.jena.fuseki.main.sys.FusekiModules;
+import org.apache.jena.fuseki.main.sys.FusekiModulesLoaded;
 import org.apache.jena.fuseki.system.FusekiLogging;
 import org.apache.jena.http.HttpEnv;
 import org.apache.jena.rdf.model.Model;
@@ -55,13 +57,19 @@ public class ExFusekiMain_3_FusekiModule {
         //
         // The file is typically put into the jar by having
         //   
src/main/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiModule
+        // For this example, we add the module directly.
         FusekiModule module = new FMod_ProvidePATCH();
-        FusekiModules.add(module);
 
+
+        List<FusekiModule> modules = new ArrayList<>();
+        modules.addAll(FusekiModulesLoaded.loaded().asList());
+        modules.add(module);
+        FusekiModules fusekiModules = FusekiModules.create(modules);
         // Create server.
         FusekiServer server =
             FusekiServer.create()
                 .port(0)
+                .setModules(fusekiModules)
                 .build()
                 .start();
         int port = server.getPort();
@@ -77,10 +85,11 @@ public class ExFusekiMain_3_FusekiModule {
 
     static class FMod_ProvidePATCH implements FusekiModule {
 
-        private String modName = UUID.randomUUID().toString();
+        public FMod_ProvidePATCH() {}
+
         @Override
         public String name() {
-            return modName;
+            return "ProvidePATCH";
         }
 
         @Override public void prepare(FusekiServer.Builder builder, 
Set<String> datasetNames, Model configModel) {
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/ExFuseki_04_CustomOperation_Module.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/ExFuseki_04_CustomOperation_Module.java
index e8ac062846..e913716f35 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/ExFuseki_04_CustomOperation_Module.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/ExFuseki_04_CustomOperation_Module.java
@@ -19,6 +19,7 @@
 package org.apache.jena.fuseki.main.examples;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.ServiceLoader;
 import java.util.Set;
 
@@ -59,11 +60,13 @@ public class ExFuseki_04_CustomOperation_Module {
     // Example usage.
     public static void main(String...args) {
 
-        // Imitate FusekiModule service loader behaviour.
-        FusekiModules.add(new FMod_Custom());
+        FusekiModule fmodCustom = new FMod_Custom();
+
+        FusekiModules modules = FusekiModules.create(List.of(fmodCustom));
 
         FusekiServer.create().port(3230)
             .add("/ds", DatasetGraphFactory.createTxnMem())
+            .setModules(modules)
             .build()
             .start();
 
@@ -75,6 +78,10 @@ public class ExFuseki_04_CustomOperation_Module {
 
         private Operation myOperation = null;
 
+        public FMod_Custom() {
+            myOperation = Operation.alloc("http://example/extra-service";, 
"extra-service", "Test");
+        }
+
         @Override
         public String name() {
             return "Custom Operation Example";
@@ -82,9 +89,9 @@ public class ExFuseki_04_CustomOperation_Module {
 
         @Override
         public void start() {
+            // Only called if loaded via the ServiceLoader.
             Fuseki.configLog.info("Add custom operation into global 
registry.");
             System.err.println("**** Fuseki extension ****");
-            myOperation = Operation.alloc("http://example/extra-service";, 
"extra-service", "Test");
         }
 
         @Override


Reply via email to