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 dd17db6825fe5bdda977c1bdcdf3e7f38f9cea83
Author: Andy Seaborne <[email protected]>
AuthorDate: Sat Dec 14 15:46:36 2024 +0000

    GH-2462: Reload action for configuration files
---
 .../main/java/org/apache/jena/fuseki/Fuseki.java   | 21 +++---
 .../jena/fuseki/ctl/ActionContainerItem.java       |  2 +-
 .../org/apache/jena/fuseki/ctl/ActionItem.java     |  4 +-
 .../apache/jena/fuseki/ctl/ActionStatsText.java    |  4 +-
 .../org/apache/jena/fuseki/ctl/ActionTasks.java    |  2 +-
 .../fuseki/server/DataAccessPointRegistry.java     | 16 ++++-
 .../org/apache/jena/fuseki/server/Dispatcher.java  |  4 +-
 .../org/apache/jena/fuseki/servlets/ActionLib.java |  4 --
 .../jena/fuseki/servlets/ActionProcessor.java      |  4 +-
 .../jena/fuseki/servlets/BaseActionREST.java       |  2 +-
 .../apache/jena/fuseki/servlets/HttpAction.java    |  4 ++
 .../apache/jena/fuseki/servlets/ServletOps.java    | 11 ++-
 .../org/apache/jena/fuseki/servlets/UploadRDF.java |  2 +-
 .../apache/jena/fuseki/system/FusekiLogging.java   |  7 +-
 .../org/apache/jena/fuseki/main/FusekiLib.java     | 39 +++++++++--
 .../org/apache/jena/fuseki/main/FusekiServer.java  | 80 +++++++++++++++++++---
 .../org/apache/jena/fuseki/main/JettyServer.java   |  1 +
 .../jena/fuseki/main/sys/FusekiModuleStep.java     |  2 +-
 .../apache/jena/fuseki/main/CustomTestService.java |  2 +-
 .../org/apache/jena/fuseki/main/TS_FusekiMain.java |  7 +-
 .../jena/fuseki/main/TestFusekiServerBuild.java    |  7 +-
 .../apache/jena/fuseki/main/TestPlainServer.java   |  2 +-
 .../jena/fuseki/main/examples/DemoService.java     |  2 +-
 .../testing/Config/reload-config1.ttl              | 16 +++++
 .../testing/Config/reload-config2.ttl              | 25 +++++++
 .../jena-fuseki-main/testing/Files/exists.txt      |  2 +
 .../jena-fuseki-main/testing/Files/file-top.txt    |  2 -
 27 files changed, 215 insertions(+), 59 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 bd7c26431c..f82f7dad8f 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
@@ -40,37 +40,33 @@ import org.slf4j.LoggerFactory;
 
 public class Fuseki {
     // General fixed constants.
-    // See also FusekiServer for the naming on the filesystem
 
     /** Path as package name */
-    static public String    PATH                         = 
"org.apache.jena.fuseki";
+    static public final String PATH               = "org.apache.jena.fuseki";
 
     /** a unique IRI for the Fuseki namespace */
-    static public String    FusekiIRI                    = 
"http://jena.apache.org/Fuseki";;
+    static public final String FusekiIRI          = 
"http://jena.apache.org/Fuseki";;
 
     /**
      * a unique IRI including the symbol notation for which properties should 
be
      * appended
      */
-    static public String    FusekiSymbolIRI              = 
"http://jena.apache.org/fuseki#";;
+    static public final String FusekiSymbolIRI    = 
"http://jena.apache.org/fuseki#";;
 
     /** Default location of the pages for the Fuseki UI  */
-    static public String    PagesStatic                  = "pages";
+    static public final String PagesStatic        = "pages";
 
     /** Dummy base URi string for parsing SPARQL Query and Update requests */
-    static public final String BaseParserSPARQL          = 
"http://server/unset-base/";;
+    static public final String BaseParserSPARQL   = 
"http://server/unset-base/";;
 
     /** Dummy base URi string for parsing SPARQL Query and Update requests */
-    static public final String BaseUpload                = 
"http://server/unset-base/";;
-
-    /** Add CORS header */
-    static public final boolean CORS_ENABLED = false;
+    static public final String BaseUpload         = 
"http://server/unset-base/";;
 
     /** The name of the Fuseki server.*/
-    static public final String        NAME              = "Apache Jena Fuseki";
+    static public final String  NAME              = "Apache Jena Fuseki";
 
     /** Version of this Fuseki instance */
-    static public final String        VERSION           = 
Version.versionForClass(Fuseki.class).orElse("<development>");
+    static public final String  VERSION           = 
Version.versionForClass(Fuseki.class).orElse("<development>");
 
     /** Supporting Graph Store Protocol direct naming.
      * <p>
@@ -178,6 +174,7 @@ public class Fuseki {
     public static final String attrNameRegistry            = 
"org.apache.jena.fuseki:DataAccessPointRegistry";
     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 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/ctl/ActionContainerItem.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionContainerItem.java
index 1d62880090..8ed56532d3 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionContainerItem.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionContainerItem.java
@@ -43,7 +43,7 @@ public abstract class ActionContainerItem extends ActionCtl {
         else if ( method.equals(METHOD_DELETE) )
             performDelete(action);
         else
-            ServletOps.errorMethodNotAllowed(action.getMethod());
+            ServletOps.errorMethodNotAllowed(action.getRequestMethod());
     }
 
     @Override
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionItem.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionItem.java
index ccc360ea47..dd32e8b50f 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionItem.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionItem.java
@@ -30,14 +30,14 @@ public abstract class ActionItem extends ActionContainerItem
     @Override
     final
     protected JsonValue execGetContainer(HttpAction action) {
-        ServletOps.errorMethodNotAllowed(action.getMethod());
+        ServletOps.errorMethodNotAllowed(action.getRequestMethod());
         return null;
     }
 
     @Override
     final
     protected JsonValue execPostContainer(HttpAction action) {
-        ServletOps.errorMethodNotAllowed(action.getMethod());
+        ServletOps.errorMethodNotAllowed(action.getRequestMethod());
         return null;
     }
 }
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStatsText.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStatsText.java
index 0ff5559b4b..a1e6b4c4a4 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStatsText.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStatsText.java
@@ -38,12 +38,12 @@ public class ActionStatsText extends ActionCtl
 
     @Override
     public void validate(HttpAction action) {
-        switch(action.getMethod() ) {
+        switch(action.getRequestMethod() ) {
             case HttpNames.METHOD_GET:
             case HttpNames.METHOD_POST:
                 return;
             default:
-                ServletOps.errorMethodNotAllowed(action.getMethod());
+                ServletOps.errorMethodNotAllowed(action.getRequestMethod());
         }
     }
 
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionTasks.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionTasks.java
index 0d573b26ce..efbe6d475e 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionTasks.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionTasks.java
@@ -62,7 +62,7 @@ public class ActionTasks extends ActionCtl
         else if ( method.equals(METHOD_POST) )
             execPost(action, name);
         else
-            ServletOps.errorMethodNotAllowed(action.getMethod());
+            ServletOps.errorMethodNotAllowed(action.getRequestMethod());
     }
 
     private void execGet(HttpAction action, String name) {
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
index b6d41f31da..02307d3e7e 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
@@ -27,6 +27,7 @@ import org.apache.jena.atlas.lib.Registry;
 import org.apache.jena.atlas.logging.Log;
 import org.apache.jena.fuseki.Fuseki;
 import org.apache.jena.fuseki.FusekiException;
+import org.apache.jena.fuseki.servlets.HttpAction;
 
 /**
  * Registry of (dataset name, {@link DataAccessPoint}).
@@ -59,7 +60,7 @@ public class DataAccessPointRegistry extends Registry<String, 
DataAccessPoint>
      */
     public List<DataAccessPoint> accessPoints() {
         List<DataAccessPoint> accessPoints = new ArrayList<>(size());
-        // Make a copy for safety.
+        // Copy for safety.
         forEach((_name, accessPoint) -> accessPoints.add(accessPoint));
         return accessPoints;
     }
@@ -90,7 +91,14 @@ public class DataAccessPointRegistry extends 
Registry<String, DataAccessPoint>
         });
     }
 
-    // The server DataAccessPointRegistry is held in the ServletContext for 
the server.
+    /** The server DataAccessPointRegistry is held in the ServletContext.
+     * <p>
+     * Reload may change this object for another one. Therefore, code should 
obtain the
+     * DataAccessPointRegistry once per operation.
+     * <p>
+     * Each request, has a stable {@link 
HttpAction#getDataAccessPointRegistry()}.
+     * <p>Getting the {@link DataAccessPointRegistry} is atomic.
+     */
     public static DataAccessPointRegistry get(ServletContext cxt) {
         DataAccessPointRegistry registry = 
(DataAccessPointRegistry)cxt.getAttribute(Fuseki.attrNameRegistry);
         if ( registry == null )
@@ -98,6 +106,10 @@ public class DataAccessPointRegistry extends 
Registry<String, DataAccessPoint>
         return registry;
     }
 
+    /**
+     * Set or change the {@link DataAccessPointRegistry}.
+     * This is atomic. (In Jetty, it is backed by a ConcurrentHashMap).
+     */
     public static void set(ServletContext cxt, DataAccessPointRegistry 
registry) {
         cxt.setAttribute(Fuseki.attrNameRegistry, registry);
     }
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Dispatcher.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Dispatcher.java
index a93c3cc3ac..8ceac84ea1 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Dispatcher.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Dispatcher.java
@@ -388,11 +388,11 @@ public class Dispatcher {
             } else if ( GSP_RW.equals(operation) ) {
                 // If asking for GSP_RW, but only GSP_R is available ...
                 // ... if OPTIONS, use GSP_R.
-                if ( action.getMethod().equals(HttpNames.METHOD_OPTIONS) && 
epSet.contains(GSP_R) )
+                if ( 
action.getRequestMethod().equals(HttpNames.METHOD_OPTIONS) && 
epSet.contains(GSP_R) )
                         return GSP_R;
                 // ... else 405
                 if ( epSet.contains(GSP_R) )
-                    ServletOps.errorMethodNotAllowed(action.getMethod());
+                    
ServletOps.errorMethodNotAllowed(action.getRequestMethod());
             }
         }
         return operation;
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
index a65dbcba99..8081749642 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
@@ -401,8 +401,6 @@ public class ActionLib {
     }
 
     private static void setCommonHeadersForOptions(HttpServletResponse 
httpResponse) {
-        if ( Fuseki.CORS_ENABLED )
-            httpResponse.setHeader(HttpNames.hAccessControlAllowHeaders, 
"X-Requested-With, Content-Type, Authorization");
         setCommonHeaders(httpResponse);
     }
 
@@ -411,8 +409,6 @@ public class ActionLib {
     }
 
     private static void setCommonHeaders(HttpServletResponse httpResponse) {
-        if ( Fuseki.CORS_ENABLED )
-            httpResponse.setHeader(HttpNames.hAccessControlAllowOrigin, "*");
         if ( Fuseki.outputFusekiServerHeader )
             httpResponse.setHeader(HttpNames.hServer, Fuseki.serverHttpName);
     }
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionProcessor.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionProcessor.java
index caad104ec4..44085722aa 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionProcessor.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionProcessor.java
@@ -29,7 +29,7 @@ public interface ActionProcessor {
      * @param action   HTTP Action
      */
     public default void process(HttpAction action) {
-        switch ( action.getMethod() ) {
+        switch ( action.getRequestMethod() ) {
             case METHOD_GET ->       execGet(action);
             case METHOD_POST ->      execPost(action);
             case METHOD_PATCH ->     execPatch(action);
@@ -38,7 +38,7 @@ public interface ActionProcessor {
             case METHOD_HEAD ->      execHead(action);
             case METHOD_OPTIONS->    execOptions(action);
             case METHOD_TRACE ->     execTrace(action);
-            default -> execAny(action.getMethod(), action);
+            default -> execAny(action.getRequestMethod(), action);
         }
     }
 
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/BaseActionREST.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/BaseActionREST.java
index b8ff4c3cdc..2797d09337 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/BaseActionREST.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/BaseActionREST.java
@@ -46,6 +46,6 @@ public class BaseActionREST extends ActionREST {
     public void validate(HttpAction action) { }
 
     private void notSupported(HttpAction action) {
-        ServletOps.errorMethodNotAllowed(action.getMethod()+" 
"+action.getDatasetName());
+        ServletOps.errorMethodNotAllowed(action.getRequestMethod()+" 
"+action.getDatasetName());
     }
 }
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
index ef3935646e..9e35cd3310 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
@@ -316,6 +316,7 @@ public class HttpAction
             transactional.begin(txnType);
         activeDSG = dsg;
         if ( dataService != null )
+            // Paired with finishTxn in end()
             dataService.startTxn(txnType);
         startActionTxn();
     }
@@ -491,6 +492,7 @@ public class HttpAction
     // ----
 
     public void commit() {
+//dataService.finishTxn();
         if ( transactional != null )
             transactional.commit();
         endInternal();
@@ -616,6 +618,8 @@ public class HttpAction
 
     // ---- Request - response abstraction.
 
+    /** @deprecated Use {@link #getRequestMethod}. */
+    @Deprecated(since="5.1.0", forRemoval=true)
     public String getMethod()                           { return 
request.getMethod(); }
 
     public HttpServletRequest getRequest()              { return request; }
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
index 74da22ed83..23a09da8a0 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
@@ -248,11 +248,11 @@ public class ServletOps {
     }
 
     public static void error(int statusCode) {
-        throw new ActionErrorException(statusCode, null, null);
+        throw actionErrorException(statusCode, null, null);
     }
 
     public static void error(int statusCode, String string) {
-        throw new ActionErrorException(statusCode, string, null);
+        throw actionErrorException(statusCode, string, null);
     }
 
     public static void errorOccurred(String message) {
@@ -266,7 +266,12 @@ public class ServletOps {
     public static void errorOccurred(String message, Throwable ex) {
         if ( ex instanceof ActionErrorException actionErr )
             throw actionErr;
-        throw new ActionErrorException(HttpSC.INTERNAL_SERVER_ERROR_500, 
message, ex);
+        throw actionErrorException(HttpSC.INTERNAL_SERVER_ERROR_500, message, 
ex);
+        /* Does not return */
+    }
+
+    private static ActionErrorException actionErrorException(int statusCode, 
String message, Throwable ex) {
+        return new ActionErrorException(statusCode, message, ex);
     }
 
     public static String formatForLog(String string) {
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/UploadRDF.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/UploadRDF.java
index 24de0d7b94..0a9af3cf76 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/UploadRDF.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/UploadRDF.java
@@ -54,7 +54,7 @@ public class UploadRDF extends ActionREST {
     @Override protected void doPatch(HttpAction action)     { 
unsupported(action); }
 
     private void unsupported(HttpAction action) {
-        ServletOps.errorMethodNotAllowed(action.getMethod());
+        ServletOps.errorMethodNotAllowed(action.getRequestMethod());
     }
 
     @Override
diff --git 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/FusekiLogging.java
 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/FusekiLogging.java
index 1c610b5cc2..b1e8cc9098 100644
--- 
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/FusekiLogging.java
+++ 
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/FusekiLogging.java
@@ -34,7 +34,7 @@ import org.apache.jena.fuseki.Fuseki;
 /**
  * FusekiLogging.
  * <p>
- * This applies to Fuseki run from the command line and embedded.
+ * This applies to Fuseki run from the command line, as a combined jar and as 
an embedded server.
  * <p>
  * This does not apply to Fuseki running in Tomcat where it uses the
  * servlet 3.0 mechanism described in
@@ -49,10 +49,9 @@ public class FusekiLogging
 
     // Set logging.
     // 1/ Use system property log4j2.configurationFile if defined.
-    // 2/ Use file:log4j2.properties if exists
+    // 2/ Use file:log4j2.properties if exists [Jena extension]
     // 3/ Use log4j2.properties on the classpath.
-    // 4/ Use org/apache/jena/fuseki/log4j2.properties on the classpath.
-    // 5/ Use built in string
+    // 4/ Use built in string
 
     /**
      * Places for the log4j properties file at (3).
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiLib.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiLib.java
index 0b0d152525..5a6730639e 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiLib.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiLib.java
@@ -21,20 +21,24 @@ package org.apache.jena.fuseki.main;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import jakarta.servlet.ServletContext;
+import org.apache.jena.atlas.logging.Log;
+import org.apache.jena.fuseki.Fuseki;
 import org.apache.jena.fuseki.access.AccessCtl_AllowGET;
 import org.apache.jena.fuseki.access.AccessCtl_Deny;
 import org.apache.jena.fuseki.access.AccessCtl_GSP_R;
 import org.apache.jena.fuseki.access.AccessCtl_SPARQL_QueryDataset;
-import org.apache.jena.fuseki.server.DataAccessPointRegistry;
-import org.apache.jena.fuseki.server.Endpoint;
-import org.apache.jena.fuseki.server.Operation;
+import org.apache.jena.fuseki.build.FusekiConfig;
+import org.apache.jena.fuseki.server.*;
 import org.apache.jena.fuseki.servlets.ActionService;
 import org.apache.jena.fuseki.servlets.GSP_RW;
 import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.rdf.model.Model;
 import org.apache.jena.riot.WebContent;
 
 /** Actions on and about a {@link FusekiServer} */
@@ -52,6 +56,34 @@ public class FusekiLib {
         return names;
     }
 
+    /**
+     * Process a configuration mode to find the DataServices and reset the 
server
+     * {@link DataAccessPointRegistry}. The only server-level setting 
processed is
+     * the {@code fuseki:services} list. Other settings are ignored.
+     */
+    public static void reload(FusekiServer server, Model configuration) {
+        DataAccessPointRegistry newRegistry = new DataAccessPointRegistry();
+        OperationRegistry operationRegistry = server.getOperationRegistry();
+
+        try {
+            List<DataAccessPoint> newDAPs = 
FusekiConfig.servicesAndDatasets(configuration.getGraph());
+            newDAPs.forEach(dap->newRegistry.register(dap));
+            FusekiServer.Builder.prepareDataServices(newRegistry, 
operationRegistry);
+            // Reload : switch DataAccessPointRegistry
+            setDataAccessPointRegistry(server, newRegistry);
+        } catch (RuntimeException ex) {
+            Log.error(Fuseki.serverLog,  "Failed to load a new configuration", 
ex);
+        }
+    }
+
+    public static void setDataAccessPointRegistry(FusekiServer server,  
DataAccessPointRegistry newRegistry) {
+        Objects.requireNonNull(server, "server");
+        Objects.requireNonNull(newRegistry, "newRegistry");
+        ServletContext cxt = server.getServletContext();
+        // This is atomic (in Jetty, it is backed by a ConcurrentHashMap).
+        DataAccessPointRegistry.set(cxt, newRegistry);
+    }
+
     /**
      * Return a {@code FusekiServer.Builder} setup for data access control.
      */
@@ -82,7 +114,6 @@ public class FusekiLib {
     public static void modifyForAccessCtl(DataAccessPointRegistry dapRegistry, 
Function<HttpAction, String> determineUser) {
         dapRegistry.forEach((name, dap) -> {
             dap.getDataService().forEachEndpoint(ep->{
-                Operation op = ep.getOperation();
                 modifyForAccessCtl(ep, determineUser);
             });
         });
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 812d26b1f2..261a10cda7 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
@@ -28,7 +28,6 @@ import java.util.*;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
-
 import jakarta.servlet.Filter;
 import jakarta.servlet.ServletContext;
 import jakarta.servlet.http.HttpServlet;
@@ -40,6 +39,8 @@ import org.apache.jena.atlas.lib.FileOps;
 import org.apache.jena.atlas.lib.IRILib;
 import org.apache.jena.atlas.lib.Pair;
 import org.apache.jena.atlas.lib.Registry;
+import org.apache.jena.atlas.logging.FmtLog;
+import org.apache.jena.atlas.logging.Log;
 import org.apache.jena.atlas.web.AuthScheme;
 import org.apache.jena.fuseki.Fuseki;
 import org.apache.jena.fuseki.FusekiConfigException;
@@ -57,7 +58,9 @@ import org.apache.jena.fuseki.servlets.*;
 import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.Node;
 import org.apache.jena.query.Dataset;
-import org.apache.jena.rdf.model.*;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.ModelFactory;
+import org.apache.jena.rdf.model.Resource;
 import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.assembler.AssemblerUtils;
 import org.apache.jena.sparql.util.Context;
@@ -162,6 +165,17 @@ public class FusekiServer {
         return new Builder(serviceDispatchRegistry, context);
     }
 
+    /**
+     * Return the {@code FusekiServer} associated with a {@link 
ServletContext}.
+     * <p>
+     * This will return null if the {@link ServletContext} is not from a 
FusekiServer.
+     */
+    public static FusekiServer get(ServletContext cxt) {
+        FusekiServer server = 
(FusekiServer)cxt.getAttribute(Fuseki.attrFusekiServer);
+        if ( server == null )
+            Log.warn(Fuseki.serverLog, "No FusekiServer registered in 
ServletContext");
+        return server;
+    }
 
     /**
      * Default port when running in Java via {@code FusekiServer....build()}.
@@ -178,18 +192,24 @@ public class FusekiServer {
     private int httpsPort;
     private final String staticContentDir;
     private final ServletContext servletContext;
+    private final String configFilename;
     private final FusekiModules modules;
 
     private FusekiServer(int httpPort, int httpsPort, Server server,
                          String staticContentDir,
                          FusekiModules modules,
+                         String configFilename,
                          ServletContext fusekiServletContext) {
         this.server = Objects.requireNonNull(server);
         this.httpPort = httpPort;
         this.httpsPort = httpsPort;
         this.staticContentDir = staticContentDir;
         this.servletContext = Objects.requireNonNull(fusekiServletContext);
+        this.configFilename = configFilename;
         this.modules = Objects.requireNonNull(modules);
+
+        // So servlets can find the server.
+        servletContext.setAttribute(Fuseki.attrFusekiServer, this);
     }
 
     /**
@@ -317,6 +337,14 @@ public class FusekiServer {
         return modules;
     }
 
+    /**
+     * Return the filename of the configuration file.
+     * Returns null if configuration was by APIs and there is no such file was 
used.
+     */
+    public String getConfigFilename() {
+        return configFilename;
+    }
+
     /**
      * Start the server - the server continues to run after this call returns.
      * To synchronise with the server stopping, call {@link #join}.
@@ -426,6 +454,9 @@ public class FusekiServer {
         private boolean                  withTasks          = false;
 
         private String                   jettyServerConfig  = null;
+
+        // Record calls to parseConfigFile.
+        private String                   configFileName     = null;
         private Model                    configModel        = null;
         private Map<String, String>      corsInitParams     = null;
 
@@ -796,12 +827,34 @@ public class FusekiServer {
         /**
          * Configure using a Fuseki services/datasets assembler file.
          * <p>
-         * The application is responsible for ensuring a correct classpath. 
For example,
-         * including a dependency on {@code jena-text} if the configuration 
file includes
-         * a text index.
+         * The configuration file is processed in this call so that subsequent
+         * builder operations can refer to data services created by this call.
+         * <p>
+         * The calling application is responsible for ensuring a correct 
classpath.
+         * For example, including a dependency on {@code jena-text} if the
+         * configuration file includes a text index.
+         */
+        public Builder parseConfigFile(Path filename) {
+            requireNonNull(filename, "filename");
+            parseConfigFile(filename.toString());
+            return this;
+        }
+
+        /**
+         * Configure using a Fuseki services/datasets assembler file.
+         * <p>
+         * The configuration file is processed in this call so that subsequent
+         * builder operations can refer to data services created by this call.
+         * <p>
+         * The calling application is responsible for ensuring a correct 
classpath.
+         * For example, including a dependency on {@code jena-text} if the
+         * configuration file includes a text index.
          */
         public Builder parseConfigFile(String filename) {
             requireNonNull(filename, "filename");
+            if ( configFileName != null )
+                FmtLog.warn(Fuseki.configLog, "Multiple configuration files 
(reload will not be possible)");
+            this.configFileName = filename;
             Model model = AssemblerUtils.readAssemblerFile(filename);
             parseConfig(model);
             return this;
@@ -1374,9 +1427,16 @@ public class FusekiServer {
                 if ( hasFusekiSecurityHandler )
                     applyAccessControl(handler, dapRegistry);
 
+
+
                 if ( jettyServerConfig != null ) {
+                    // Jetty server configuration provided.
                     Server server = jettyServer(handler, jettyServerConfig);
-                    return new FusekiServer(-1, -1, server, staticContentDir, 
modules, handler.getServletContext());
+                    return new FusekiServer(-1, -1, server,
+                                            staticContentDir,
+                                            modules,
+                                            configFileName,
+                                            handler.getServletContext());
                 }
 
                 Server server;
@@ -1401,7 +1461,11 @@ public class FusekiServer {
                 if ( networkLoopback )
                     applyLocalhost(server);
 
-                FusekiServer fusekiServer = new FusekiServer(httpPort, 
httpsPort, server, staticContentDir, modules, handler.getServletContext());
+                FusekiServer fusekiServer = new FusekiServer(httpPort, 
httpsPort, server,
+                                                             staticContentDir,
+                                                             modules,
+                                                             configFileName,
+                                                             
handler.getServletContext());
                 FusekiModuleStep.server(fusekiServer);
                 return fusekiServer;
             } finally {
@@ -1442,7 +1506,7 @@ public class FusekiServer {
             return handler;
         }
 
-        private static void prepareDataServices(DataAccessPointRegistry 
dapRegistry, OperationRegistry operationReg) {
+        /*package*/ static void prepareDataServices(DataAccessPointRegistry 
dapRegistry, OperationRegistry operationReg) {
             dapRegistry.forEach((name, dap) -> {
                 // Override for graph-level access control.
                 if ( 
DataAccessCtl.isAccessControlled(dap.getDataService().getDataset()) ) {
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/JettyServer.java
 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/JettyServer.java
index 80641c76f6..fb65a1044f 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/JettyServer.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/JettyServer.java
@@ -393,6 +393,7 @@ public class JettyServer {
             try {
                 Fuseki.setVerbose(cxt, verbose);
                 OperationRegistry.set(cxt, OperationRegistry.createEmpty());
+                // Empty DataAccessPointRegistry, no nulls!
                 DataAccessPointRegistry.set(cxt, new 
DataAccessPointRegistry());
             } catch (NoClassDefFoundError err) {
                 LOG.info("Fuseki classes not found");
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 7735c187b4..1a320f5d21 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
@@ -77,7 +77,7 @@ public class FusekiModuleStep {
     }
 
     /**
-     * Sever reload.
+     * Server reload.
      * Return true if reload happened, else false.
      * @see FusekiBuildCycle#serverConfirmReload
      * @see FusekiBuildCycle#serverReload
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/CustomTestService.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/CustomTestService.java
index a7b8cdbf18..4fc40a60c2 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/CustomTestService.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/CustomTestService.java
@@ -76,6 +76,6 @@ public class CustomTestService extends ActionREST {
     public void validate(HttpAction action) { }
 
     private void notSupported(HttpAction action) {
-        ServletOps.errorMethodNotAllowed(action.getMethod()+" 
"+action.getDatasetName());
+        ServletOps.errorMethodNotAllowed(action.getRequestMethod()+" 
"+action.getDatasetName());
     }
 }
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 e0fa22d5e8..92348e10d1 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
@@ -18,11 +18,12 @@
 
 package org.apache.jena.fuseki.main;
 
-import org.apache.jena.fuseki.main.prefixes.*;
-import org.apache.jena.fuseki.main.sys.TestFusekiModules;
 import org.junit.platform.suite.api.SelectClasses;
 import org.junit.platform.suite.api.Suite;
 
+import org.apache.jena.fuseki.main.prefixes.PrefixesServiceTests;
+import org.apache.jena.fuseki.main.sys.TestFusekiModules;
+
 @Suite
 @SelectClasses({
 
@@ -55,9 +56,9 @@ import org.junit.platform.suite.api.Suite;
   , TestFusekiCustomScriptFunc.class
 
   , PrefixesServiceTests.class
-
   , TestMetrics.class
   , TestFusekiShaclValidation.class
+
 })
 public class TS_FusekiMain {}
 
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiServerBuild.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiServerBuild.java
index cefa3ae31d..611ca3a80e 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiServerBuild.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiServerBuild.java
@@ -27,6 +27,7 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 import java.util.function.Consumer;
 
+import jakarta.servlet.ServletContext;
 import org.apache.jena.atlas.iterator.Iter;
 import org.apache.jena.atlas.logging.Log;
 import org.apache.jena.atlas.logging.LogCtl;
@@ -60,9 +61,13 @@ public class TestFusekiServerBuild {
 
     @Test public void fuseki_build_1() {
         FusekiServer server = FusekiServer.create().port(3456).build();
-        // Not started. Port not assigned.
         assertTrue(server.getHttpPort() == 3456 );
         assertTrue(server.getHttpsPort() == -1 );
+
+        ServletContext cxt = server.getServletContext();
+        FusekiServer server2 = FusekiServer.get(cxt);
+        assertNotNull(server2);
+        assertEquals(server, server2);
     }
 
     @Test public void fuseki_build_2() {
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPlainServer.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPlainServer.java
index 7aec058fa8..424cda1007 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPlainServer.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPlainServer.java
@@ -109,7 +109,7 @@ public class TestPlainServer {
     }
 
     @Test public void plainFile3() {
-        String x = HttpOp.httpGetString(serverURL+"/file-top.txt");
+        String x = HttpOp.httpGetString(serverURL+"/exists.txt");
         assertNotNull(x);
         assertTrue(x.contains("CONTENT"));
     }
diff --git 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/DemoService.java
 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/DemoService.java
index b83273b764..6db9b378ca 100644
--- 
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/DemoService.java
+++ 
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/examples/DemoService.java
@@ -79,6 +79,6 @@ public class DemoService extends ActionREST {
     public void validate(HttpAction action) { }
 
     private void notSupported(HttpAction action) {
-        ServletOps.errorMethodNotAllowed(action.getMethod()+" 
"+action.getActionURI());
+        ServletOps.errorMethodNotAllowed(action.getRequestMethod()+" 
"+action.getActionURI());
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/reload-config1.ttl 
b/jena-fuseki2/jena-fuseki-main/testing/Config/reload-config1.ttl
new file mode 100644
index 0000000000..3e916dbfe8
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/testing/Config/reload-config1.ttl
@@ -0,0 +1,16 @@
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "ds" ;
+    fuseki:endpoint [ fuseki:operation fuseki:query ; ] ;
+    fuseki:endpoint [ fuseki:operation fuseki:update ; ] ;
+    fuseki:endpoint [ fuseki:operation fuseki:gsp-rw ; ] ;
+    fuseki:endpoint [ fuseki:operation fuseki:patch ; ] ;
+    fuseki:dataset :dataset ;
+.
+
+:dataset rdf:type ja:MemoryDataset .
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/reload-config2.ttl 
b/jena-fuseki2/jena-fuseki-main/testing/Config/reload-config2.ttl
new file mode 100644
index 0000000000..bad6403a74
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/testing/Config/reload-config2.ttl
@@ -0,0 +1,25 @@
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset2" ;
+    fuseki:endpoint [ fuseki:operation fuseki:query ; ] ;
+    fuseki:endpoint [ fuseki:operation fuseki:update ; ] ;
+    fuseki:endpoint [ fuseki:operation fuseki:gsp-rw ; ] ;
+    fuseki:endpoint [ fuseki:operation fuseki:patch ; ] ;
+    fuseki:dataset :dataset ;
+.
+
+:dataset rdf:type ja:MemoryDataset ;
+     #ja:data "data.trig";
+.
+
+:service2 rdf:type fuseki:Service ;
+    fuseki:name "null" ;
+    ## No operations.
+    ## Always empty dataset.
+    fuseki:dataset [ rdf:type ja:RDFDatasetZero ] ;
+    .
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Files/exists.txt 
b/jena-fuseki2/jena-fuseki-main/testing/Files/exists.txt
new file mode 100644
index 0000000000..4c5132ec87
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/testing/Files/exists.txt
@@ -0,0 +1,2 @@
+This file is used by the plain server static resource tests.
+CONTENT
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Files/file-top.txt 
b/jena-fuseki2/jena-fuseki-main/testing/Files/file-top.txt
deleted file mode 100644
index a980c835bc..0000000000
--- a/jena-fuseki2/jena-fuseki-main/testing/Files/file-top.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-CONTENT


Reply via email to