You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2019/06/07 11:16:21 UTC

[jena] branch master updated: JENA-1715: Fuseki dispatch

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2299725  JENA-1715: Fuseki dispatch
     new 5bf2bed  Merge pull request #573 from afs/fuseki-dispatch
2299725 is described below

commit 229972507a2dab26a8572a8f537fa33c77c50cd4
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sun May 5 17:24:06 2019 +0100

    JENA-1715: Fuseki dispatch
---
 .../org/apache/jena/atlas/json/JsonObject.java     |  28 +-
 .../org/apache/jena/atlas/web/HttpException.java   |  21 +-
 .../main/java/org/apache/jena/riot/web/HttpOp.java |   8 +-
 .../apache/jena/sparql/core/DatasetGraphBase.java  |   8 +-
 .../jena/sparql/core/DatasetGraphWrapper.java      |   7 +-
 .../apache/jena/sparql/engine/http/HttpQuery.java  |  22 +-
 .../sparql/engine/http/QueryExceptionHTTP.java     |  29 +-
 .../apache/jena/web/DatasetGraphAccessorHTTP.java  |   6 +-
 .../src/main/java/org/apache/jena/web/HttpSC.java  |   7 +-
 .../org/apache/jena/atlas/io/InStreamUTF8.java     |  58 +-
 .../org/apache/jena/atlas/io/OutStreamUTF8.java    |  58 +-
 .../java/org/apache/jena/atlas/lib/Registry.java   |   1 -
 .../jena/fuseki/access/AccessCtl_AllowGET.java     |  74 +++
 ...PARQL_Update.java => AccessCtl_DenyUpdate.java} |  56 +-
 ...sCtl_SPARQL_GSP_R.java => AccessCtl_GSP_R.java} |   8 +-
 .../jena/fuseki/access/AccessCtl_REST_Quads_R.java |  43 --
 .../fuseki/access/AccessCtl_REST_Quads_RW.java     |  49 --
 .../fuseki/access/AccessCtl_SPARQL_GSP_RW.java     |  49 --
 .../access/AccessCtl_SPARQL_QueryDataset.java      |  29 +-
 .../fuseki/access/AccessCtl_SPARQL_Upload.java     |  49 --
 .../jena/fuseki/access/AssemblerAccessDataset.java |  28 +-
 .../fuseki/access/AssemblerSecurityRegistry.java   |  66 +--
 .../jena/fuseki/access/AuthorizationService.java   |   2 +-
 .../apache/jena/fuseki/access/DataAccessCtl.java   |  22 +-
 .../apache/jena/fuseki/access/DataAccessLib.java   |  12 +-
 .../fuseki/access/DatasetGraphAccessControl.java   |  16 +-
 .../org/apache/jena/fuseki/access/GraphFilter.java |  16 +-
 .../apache/jena/fuseki/access/GraphFilterTDB1.java |   8 +-
 .../apache/jena/fuseki/access/GraphFilterTDB2.java |   6 +-
 .../apache/jena/fuseki/access/InitSecurity.java    |   6 +-
 .../apache/jena/fuseki/access/SecurityContext.java |  18 +-
 .../fuseki/access/SecurityContextAllowAll.java     |  12 +-
 .../access/SecurityContextAllowNamedGraphs.java    |  12 +-
 .../fuseki/access/SecurityContextAllowNone.java    |  16 +-
 .../jena/fuseki/access/SecurityContextView.java    |  24 +-
 .../jena/fuseki/access/SecurityRegistry.java       |  14 +-
 .../apache/jena/fuseki/access/VocabSecurity.java   |  12 +-
 .../org/apache/jena/fuseki/access/TS_Access.java   |   2 +-
 .../fuseki/access/TestSecurityFilterLocal.java     |  80 +--
 .../jena/fuseki/access/TestSecurityRegistry.java   |  14 +-
 jena-fuseki2/jena-fuseki-core/pom.xml              |  15 +-
 .../src/main/java/org/apache/jena/fuseki/DEF.java  |  65 +--
 .../main/java/org/apache/jena/fuseki/Fuseki.java   | 191 +++----
 .../apache/jena/fuseki/FusekiConfigException.java  |   8 +-
 .../org/apache/jena/fuseki/FusekiException.java    |  10 +-
 .../org/apache/jena/fuseki/async/AsyncPool.java    |  82 +--
 .../org/apache/jena/fuseki/async/AsyncTask.java    | 108 ++--
 .../java/org/apache/jena/fuseki/auth/Auth.java     |  22 +-
 .../org/apache/jena/fuseki/auth/AuthPolicy.java    |   2 +-
 .../apache/jena/fuseki/auth/AuthPolicyList.java    |  18 +-
 .../org/apache/jena/fuseki/auth/AuthUserList.java  |  10 +-
 .../java/org/apache/jena/fuseki/auth/Users.java    |   6 +-
 .../build/{FusekiBuildLib.java => BuildLib.java}   | 120 ++--
 ...ionRegistry.java => DatasetDescriptionMap.java} |  24 +-
 .../apache/jena/fuseki/build/FusekiBuilder.java    | 172 ------
 .../org/apache/jena/fuseki/build/FusekiConfig.java | 537 +++++++++++-------
 .../{FusekiConst.java => FusekiPrefixes.java}      |   6 +-
 .../apache/jena/fuseki/ctl/ActionAsyncTask.java    |  40 +-
 .../jena/fuseki/ctl/ActionContainerItem.java       | 124 ++---
 .../java/org/apache/jena/fuseki/ctl/ActionCtl.java | 118 ++--
 .../apache/jena/fuseki/ctl/ActionDumpRequest.java  |  37 +-
 .../org/apache/jena/fuseki/ctl/ActionItem.java     |  18 +-
 .../org/apache/jena/fuseki/ctl/ActionMetrics.java  |  25 +-
 .../org/apache/jena/fuseki/ctl/ActionPing.java     |  44 +-
 .../org/apache/jena/fuseki/ctl/ActionSleep.java    |  84 +--
 .../org/apache/jena/fuseki/ctl/ActionStats.java    | 261 ++++-----
 .../org/apache/jena/fuseki/ctl/ActionTasks.java    | 121 ++--
 .../java/org/apache/jena/fuseki/ctl/Async.java     |  50 +-
 .../org/apache/jena/fuseki/ctl/JsonConstCtl.java   |  10 +-
 .../apache/jena/fuseki/ctl/JsonDescription.java    |  68 +--
 .../java/org/apache/jena/fuseki/ctl/TaskBase.java  |  24 +-
 .../jena/fuseki/jetty/FusekiErrorHandler.java      |  64 +--
 .../jena/fuseki/jetty/FusekiErrorHandler1.java     |  49 +-
 .../org/apache/jena/fuseki/jetty/JettyHttps.java   |  16 +-
 .../org/apache/jena/fuseki/jetty/JettyLib.java     |  34 +-
 .../jena/fuseki/jetty/JettyServerConfig.java       |  30 +-
 .../java/org/apache/jena/fuseki/package-info.java  |   8 +-
 .../org/apache/jena/fuseki/server/Counter.java     |  14 +-
 .../org/apache/jena/fuseki/server/CounterName.java |  20 +-
 .../org/apache/jena/fuseki/server/CounterSet.java  |  37 +-
 .../org/apache/jena/fuseki/server/Counters.java    |   4 +-
 .../apache/jena/fuseki/server/DataAccessPoint.java |  49 +-
 .../fuseki/server/DataAccessPointRegistry.java     |  40 +-
 .../org/apache/jena/fuseki/server/DataService.java | 153 ++++--
 .../jena/fuseki/server/DataServiceStatus.java      |  24 +-
 .../apache/jena/fuseki/server/DatasetMXBean.java   |  35 --
 .../org/apache/jena/fuseki/server/Endpoint.java    |  14 +-
 .../org/apache/jena/fuseki/server/EndpointSet.java | 119 ++++
 .../org/apache/jena/fuseki/server/FusekiInfo.java  |  34 +-
 .../jena/fuseki/server/FusekiInitialConfig.java    |  54 +-
 .../org/apache/jena/fuseki/server/FusekiVocab.java |  21 +-
 .../org/apache/jena/fuseki/server/NameMgr.java     |   4 +-
 .../org/apache/jena/fuseki/server/Operation.java   |  37 +-
 .../org/apache/jena/fuseki/server/RequestLog.java  | 142 ++---
 .../org/apache/jena/fuseki/server/ServerConst.java |  30 +-
 .../org/apache/jena/fuseki/server/ServiceOnly.java |  44 --
 .../apache/jena/fuseki/servlets/ActionBase.java    | 283 ++--------
 .../jena/fuseki/servlets/ActionErrorException.java |  16 +-
 .../apache/jena/fuseki/servlets/ActionExecLib.java | 310 +++++++++++
 .../org/apache/jena/fuseki/servlets/ActionLib.java | 248 ++++++---
 .../ActionLifecycle.java}                          |  15 +-
 .../jena/fuseki/servlets/ActionProcessor.java      |  52 ++
 .../apache/jena/fuseki/servlets/ActionREST.java    | 156 +++---
 .../apache/jena/fuseki/servlets/ActionService.java | 282 +---------
 .../jena/fuseki/servlets/CrossOriginFilter.java    |  10 +-
 .../apache/jena/fuseki/servlets/Dispatcher.java    | 416 ++++++++++++++
 .../apache/jena/fuseki/servlets/FusekiFilter.java  |  85 +--
 .../org/apache/jena/fuseki/servlets/GSPTarget.java | 105 ++++
 .../org/apache/jena/fuseki/servlets/GSP_Base.java  | 164 ++++++
 .../org/apache/jena/fuseki/servlets/GSP_R.java     | 177 ++++++
 .../org/apache/jena/fuseki/servlets/GSP_RW.java    | 364 ++++++++++++
 .../apache/jena/fuseki/servlets/HttpAction.java    | 386 ++++++-------
 .../servlets/HttpServletResponseTracker.java       |  20 +-
 ...ispatchRegistry.java => OperationRegistry.java} |  84 ++-
 .../apache/jena/fuseki/servlets/REST_Quads.java    |  62 ---
 .../apache/jena/fuseki/servlets/REST_Quads_R.java  | 121 ----
 .../apache/jena/fuseki/servlets/REST_Quads_RW.java | 150 -----
 .../jena/fuseki/servlets/ResponseCallback.java     |   4 +-
 .../jena/fuseki/servlets/ResponseDataset.java      | 122 ++---
 .../apache/jena/fuseki/servlets/ResponseJson.java  | 133 ++---
 .../apache/jena/fuseki/servlets/ResponseOps.java   |  90 ++-
 .../jena/fuseki/servlets/ResponseResultSet.java    | 190 +++----
 .../jena/fuseki/servlets/SPARQLProtocol.java       | 104 ++++
 ...SPARQL_Query.java => SPARQLQueryProcessor.java} | 290 +++++-----
 .../apache/jena/fuseki/servlets/SPARQL_GSP.java    | 225 --------
 .../apache/jena/fuseki/servlets/SPARQL_GSP_R.java  | 140 -----
 .../apache/jena/fuseki/servlets/SPARQL_GSP_RW.java | 216 --------
 .../jena/fuseki/servlets/SPARQL_Protocol.java      | 111 ----
 .../jena/fuseki/servlets/SPARQL_QueryDataset.java  |  51 +-
 .../jena/fuseki/servlets/SPARQL_QueryGeneral.java  | 246 +++++----
 .../apache/jena/fuseki/servlets/SPARQL_Update.java | 278 +++++-----
 .../apache/jena/fuseki/servlets/SPARQL_Upload.java | 136 +++--
 .../apache/jena/fuseki/servlets/ServiceRouter.java | 360 ------------
 .../apache/jena/fuseki/servlets/ServletAction.java |  62 +++
 .../apache/jena/fuseki/servlets/ServletBase.java   |  69 +--
 .../apache/jena/fuseki/servlets/ServletOps.java    | 153 +++---
 .../jena/fuseki/servlets/ServletProcessor.java     |  60 ++
 .../java/org/apache/jena/fuseki/system/ConNeg.java | 217 ++++----
 .../apache/jena/fuseki/system/FusekiLogging.java   | 132 ++---
 .../apache/jena/fuseki/system/FusekiNetLib.java    | 167 +++---
 .../apache/jena/fuseki/system/GraphLoadUtils.java  |  63 +--
 .../jena/fuseki/system/StreamRDFLimited.java       |  36 +-
 .../java/org/apache/jena/fuseki/system/Upload.java | 258 ++++-----
 .../apache/jena/fuseki/system/UploadDetails.java   |  74 +--
 .../jena/fuseki/system/UploadDetailsWithName.java  |  12 +-
 .../jena/fuseki/validation/ValidatorBase.java      | 154 +++---
 .../jena/fuseki/validation/ValidatorBaseJson.java  | 230 ++++----
 .../fuseki/validation/html/DataValidatorHTML.java  | 236 ++++----
 .../fuseki/validation/html/IRIValidatorHTML.java   | 127 +++--
 .../fuseki/validation/html/QueryValidatorHTML.java | 300 +++++-----
 .../validation/html/UpdateValidatorHTML.java       | 152 +++---
 .../fuseki/validation/html/ValidatorHtmlLib.java   | 148 +++--
 .../fuseki/validation/json/DataValidatorJSON.java  | 108 ++--
 .../fuseki/validation/json/IRIValidatorJSON.java   | 112 ++--
 .../fuseki/validation/json/QueryValidatorJSON.java | 154 +++---
 .../validation/json/UpdateValidatorJSON.java       |  94 ++--
 .../fuseki/validation/json/ValidationAction.java   |  95 ++--
 .../fuseki/validation/json/ValidatorJsonLib.java   |  69 ++-
 .../test/java/org/apache/jena/fuseki/Dummy.java    |   4 +-
 .../org/apache/jena/fuseki/test/FusekiTest.java    | 150 +++++
 jena-fuseki2/jena-fuseki-main/pom.xml              |   8 +
 .../org/apache/jena/fuseki/main/FusekiLib.java     |  64 +--
 .../org/apache/jena/fuseki/main/FusekiServer.java  | 303 ++++++----
 .../org/apache/jena/fuseki/main/JettyServer.java   |  21 +-
 .../apache/jena/fuseki/main/cmds/FusekiMain.java   | 150 ++---
 .../jena/fuseki/main/cmds/FusekiMainCmd.java       |   6 +-
 .../apache/jena/fuseki/main/cmds/PlatformInfo.java |  22 +-
 .../apache/jena/fuseki/main/cmds/ServerConfig.java |  22 +-
 .../apache/jena/fuseki/main/CustomTestService.java |   6 +-
 .../org/apache/jena/fuseki/main/FusekiTestLib.java | 119 ++++
 .../apache/jena/fuseki/main/FusekiTestServer.java  | 146 ++---
 .../main/{TC_Fuseki.java => TC_FusekiMain.java}    |  13 +-
 .../org/apache/jena/fuseki/main/TS_FusekiMain.java |  24 +-
 .../jena/fuseki/main/TestEmbeddedFuseki.java       | 387 +++++++------
 .../fuseki/main/TestFusekiCustomOperation.java     |  52 +-
 .../apache/jena/fuseki/main/TestFusekiMainCmd.java |  18 +-
 .../jena/fuseki/main/TestFusekiTestServer.java     |  61 ---
 .../jena/fuseki/main/TestMultipleEmbedded.java     | 170 +++---
 .../org/apache/jena/fuseki/main/TestStdSetup.java  | 170 ++++++
 .../AbstractTestFusekiSecurityAssembler.java       |  71 +--
 .../access/AbstractTestServiceDatasetAuth.java     | 112 ++++
 .../jena/fuseki/main/access/AccessTestLib.java     |  24 +-
 .../jena/fuseki/main/access/TS_SecurityFuseki.java |  17 +-
 .../jena/fuseki/main/access/TestAuthorized.java    |  26 +-
 .../jena/fuseki/main/access/TestPasswdOnly.java    | 103 ++++
 .../main/access/TestSecurityBuilderSetup.java      |  50 +-
 .../fuseki/main/access/TestSecurityConfig.java     |  52 +-
 .../main/access/TestSecurityFilterFuseki.java      |  68 +--
 .../main/access/TestServiceDataAuthBuild.java}     |  31 +-
 .../main/access/TestServiceDataAuthConfig.java     |  60 ++
 .../jena/fuseki/main/examples/ExampleService.java  |   6 +-
 .../main/examples/ExtendFuseki_AddService_1.java   |  46 +-
 .../main/examples/ExtendFuseki_AddService_2.java   |  48 +-
 .../main/examples/ExtendFuseki_AddService_3.java   |  36 +-
 .../jena/fuseki/main/{ => old}/FusekiTestAuth.java | 115 ++--
 .../TestFusekiTestAuthOld.java}                    |  38 +-
 .../testing/Access/config-auth.ttl                 |  44 ++
 .../jena-fuseki-main/testing/Access/passwd         |   4 +-
 jena-fuseki2/jena-fuseki-webapp/pom.xml            |   8 +
 .../jena/fuseki/authz/AuthorizationFilter403.java  |  44 +-
 .../org/apache/jena/fuseki/authz/DenyFilter.java   |   8 +-
 .../apache/jena/fuseki/authz/LocalhostFilter.java  |  30 +-
 .../java/org/apache/jena/fuseki/cmd/FusekiCmd.java | 324 +++++------
 .../apache/jena/fuseki/cmd/JettyFusekiWebapp.java  | 292 +++++-----
 .../org/apache/jena/fuseki/mgt/ActionBackup.java   |  44 +-
 .../apache/jena/fuseki/mgt/ActionBackupList.java   |  88 +--
 .../org/apache/jena/fuseki/mgt/ActionDatasets.java | 484 ++++++++--------
 .../org/apache/jena/fuseki/mgt/ActionLogs.java     |  48 +-
 .../apache/jena/fuseki/mgt/ActionServerStatus.java |  91 +--
 .../java/org/apache/jena/fuseki/mgt/Backup.java    |  98 ++--
 .../org/apache/jena/fuseki/mgt/ServerMgtConst.java |  26 +-
 .../java/org/apache/jena/fuseki/mgt/Template.java  |  51 +-
 .../apache/jena/fuseki/mgt/TemplateFunctions.java  |  60 +-
 .../org/apache/jena/fuseki/webapp/FusekiEnv.java   | 144 ++---
 .../fuseki/webapp/FusekiServerEnvironmentInit.java |  22 +-
 .../jena/fuseki/webapp/FusekiServerListener.java   |  60 +-
 .../apache/jena/fuseki/webapp/FusekiWebapp.java    | 419 +++++++-------
 .../jena/fuseki/webapp/ShiroEnvironmentLoader.java | 140 ++---
 .../org/apache/jena/fuseki/webapp/SystemState.java |  84 +--
 .../apache/jena/fuseki/webapp/templates/config-mem |   4 +
 .../jena/fuseki/webapp/templates/config-service    |  23 -
 .../apache/jena/fuseki/webapp/templates/config-tdb |  21 +-
 .../jena/fuseki/webapp/templates/config-tdb-dir    |  20 +-
 .../jena/fuseki/webapp/templates/config-tdb-mem    |  20 +-
 .../jena/fuseki/webapp/templates/config-tdb2       |  21 +-
 .../jena/fuseki/webapp/templates/config-tdb2-dir   |  21 +-
 .../jena/fuseki/webapp/templates/config-tdb2-mem   |  22 +-
 .../org/apache/jena/fuseki/AbstractFusekiTest.java |  16 +-
 .../java/org/apache/jena/fuseki/FileSender.java    |  72 +--
 .../java/org/apache/jena/fuseki/FusekiTest.java    | 101 ----
 .../java/org/apache/jena/fuseki/ServerCtl.java     | 216 ++++----
 .../java/org/apache/jena/fuseki/ServerTest.java    |  34 +-
 .../org/apache/jena/fuseki/TS_FusekiWebapp.java    |  34 +-
 .../java/org/apache/jena/fuseki/TestAdmin.java     | 608 +++++++++++----------
 .../java/org/apache/jena/fuseki/TestAdminAPI.java  |  38 +-
 .../test/java/org/apache/jena/fuseki/TestAuth.java |  96 ++--
 .../java/org/apache/jena/fuseki/TestBuilder.java   |   8 +-
 .../jena/fuseki/TestDatasetAccessorHTTP.java       | 456 ++++++++--------
 .../jena/fuseki/TestDatasetGraphAccessorHTTP.java  |  18 +-
 .../org/apache/jena/fuseki/TestDatasetOps.java     | 137 +++--
 .../org/apache/jena/fuseki/TestFileUpload.java     | 176 +++---
 .../java/org/apache/jena/fuseki/TestHttpOp.java    | 199 +++----
 .../org/apache/jena/fuseki/TestHttpOperations.java |  90 +--
 .../org/apache/jena/fuseki/TestHttpOptions.java    |  25 +-
 .../java/org/apache/jena/fuseki/TestQuery.java     | 164 +++---
 .../org/apache/jena/fuseki/TestSPARQLProtocol.java |  12 +-
 .../org/apache/jena/fuseki/TestServerReadOnly.java |  88 +--
 jena-jdbc/jena-jdbc-driver-remote/pom.xml          |   6 +-
 .../jena/jdbc/remote/RemoteEndpointDriver.java     |   8 +-
 .../TestRemoteEndpointConnectionWithAuth.java      |   2 +-
 .../results/TestRemoteEndpointResultsWithAuth.java |   2 +-
 .../jena/rdfconnection/RDFConnectionRemote.java    |   2 +-
 252 files changed, 11402 insertions(+), 10766 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonObject.java b/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonObject.java
index b7e89db..b134122 100644
--- a/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonObject.java
+++ b/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonObject.java
@@ -18,9 +18,10 @@
 
 package org.apache.jena.atlas.json;
 
-import java.util.* ;
+import java.util.*;
 import java.util.Map.Entry ;
 import java.util.function.BiConsumer ;
+import java.util.stream.Stream;
 
 public class JsonObject extends JsonValue
 {
@@ -71,6 +72,31 @@ public class JsonObject extends JsonValue
         return get(key).getAsObject() ;
     }
 
+    /** For walking structures */
+    public Number getNumber(String key) {
+        return get(key).getAsNumber().value();
+    }
+
+    /** For walking structures */
+    public String getString(String key) {
+        return get(key).getAsString().value();
+    }
+
+    /** For walking structures */
+    public boolean getBoolean(String key) {
+        return get(key).getAsBoolean().value();
+    }
+
+    /** For walking structures */
+    public Stream<JsonValue> getArray(String key) {
+        return get(key).getAsArray().stream();
+    }
+
+    /** For walking structures */
+    public Iterator<JsonValue> getIterator(String key) {
+        return get(key).getAsArray().iterator();
+    }
+
     public boolean isEmpty() {
         return map.isEmpty() ;
     }
diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/web/HttpException.java b/jena-arq/src/main/java/org/apache/jena/atlas/web/HttpException.java
index 212c64f..1ce157c 100644
--- a/jena-arq/src/main/java/org/apache/jena/atlas/web/HttpException.java
+++ b/jena-arq/src/main/java/org/apache/jena/atlas/web/HttpException.java
@@ -23,14 +23,13 @@ package org.apache.jena.atlas.web;
  * 
  */
 public class HttpException extends RuntimeException {
-    private static final long serialVersionUID = -7224224620679594095L;
-    private int responseCode = -1;
+    private int statusCode = -1;
     private String statusLine = null ;
 	private String response;
 
-	public HttpException(int responseCode, String statusLine, String response) {
-		super(responseCode + " - " + statusLine);
-		this.responseCode = responseCode;
+	public HttpException(int statusCode, String statusLine, String response) {
+		super(statusCode + " - " + statusLine);
+		this.statusCode = statusCode;
 		this.statusLine = statusLine ;
 		this.response = response;
 	}
@@ -49,11 +48,21 @@ public class HttpException extends RuntimeException {
     }
     
     /**
+     * Gets the status code, may be -1 if unknown
+     * @return Status Code if known, -1 otherwise
+     */
+    public int getStatusCode() {
+        return this.statusCode;
+    }
+
+    /**
      * Gets the response code, may be -1 if unknown
      * @return Response Code if known, -1 otherwise
+     * @deprecated Use {@link #getStatusCode()}
      */
+    @Deprecated
     public int getResponseCode() {
-        return this.responseCode;
+        return getStatusCode();
     }
     
     /**
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/web/HttpOp.java b/jena-arq/src/main/java/org/apache/jena/riot/web/HttpOp.java
index 412e372..8a06cf5 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/web/HttpOp.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/web/HttpOp.java
@@ -380,7 +380,7 @@ public class HttpOp {
         try {
             execHttpGet(url, acceptHeader, handler, httpClient, httpContext);
         } catch (HttpException ex) {
-            if (ex.getResponseCode() == HttpSC.NOT_FOUND_404)
+            if (ex.getStatusCode() == HttpSC.NOT_FOUND_404)
                 return null;
             throw ex;
         }
@@ -414,7 +414,7 @@ public class HttpOp {
         try {
             execHttpGet(url, acceptHeader, handler);
         } catch (HttpException ex) {
-            if (ex.getResponseCode() == HttpSC.NOT_FOUND_404)
+            if (ex.getStatusCode() == HttpSC.NOT_FOUND_404)
                 return null;
             throw ex;
         }
@@ -479,7 +479,7 @@ public class HttpOp {
         try {
             execHttpPost(url, contentType, content, acceptType, handler, httpClient, httpContext);
         } catch (HttpException ex) {
-            if (ex.getResponseCode() == HttpSC.NOT_FOUND_404)
+            if (ex.getStatusCode() == HttpSC.NOT_FOUND_404)
                 return null;
             throw ex;
         }
@@ -810,7 +810,7 @@ public class HttpOp {
         try {
             execHttpPostForm(url, params, acceptHeader, handler, httpClient, httpContext);
         } catch (HttpException ex) {
-            if (ex.getResponseCode() == HttpSC.NOT_FOUND_404)
+            if (ex.getStatusCode() == HttpSC.NOT_FOUND_404)
                 return null;
             throw ex;
         }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphBase.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphBase.java
index 9b4f4e5..1caa874 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphBase.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphBase.java
@@ -68,10 +68,10 @@ abstract public class DatasetGraphBase implements DatasetGraph
     
     @Override
     public Graph getUnionGraph() {
-        // Implementations are encouraged to implement an efficent
-        // named graph for Quad.unionGraph, and this operation that
-        // does not require the full "distinct()" used by the general purpose
-        // GraphUnionRead. See also
+        // Implementations are encouraged to implement an efficient named graph for
+        // Quad.unionGraph that does not require the full "distinct()" used by this
+        // general purpose implementation.
+        // See also
         // {@code DatasetGraphBase.findQuadsInUnionGraph} and
         // {@code findNG(Quad.unionGraph, Node.ANY, Node.ANY, Node.ANY)}
         return GraphOps.unionGraph(this);
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphWrapper.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphWrapper.java
index 4249248..c47c28a 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphWrapper.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphWrapper.java
@@ -69,11 +69,12 @@ public class DatasetGraphWrapper implements DatasetGraph, Sync
     }
 
     /** The dataset to use for redirection - can be overridden.
-     *  It is also guarantee that this is called only once per
+     *  It is also guaranteed that this is called only once per
      *  delegated call.  Changes to the wrapped object can be
-     *  made based on that contract. 
+     *  made based on that contract.
      */
     protected DatasetGraph get() { return dsg; }
+    
     protected Context getCxt()   { return context; }
 
     /** For operations that only read the DatasetGraph. */ 
@@ -213,7 +214,7 @@ public class DatasetGraphWrapper implements DatasetGraph, Sync
     @Override
     public void sync() {
         // Pass down sync.
-        SystemARQ.sync(getW()); 
+        SystemARQ.sync(getW());
     }
 
     @Override
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/HttpQuery.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/HttpQuery.java
index 427ddbb..860af23 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/HttpQuery.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/HttpQuery.java
@@ -18,6 +18,8 @@
 
 package org.apache.jena.sparql.engine.http;
 
+import static org.apache.jena.web.HttpSC.*;
+
 import java.io.InputStream ;
 import java.net.MalformedURLException ;
 import java.net.URL ;
@@ -34,9 +36,12 @@ import org.apache.jena.atlas.web.TypedInputStream ;
 import org.apache.jena.query.ARQ ;
 import org.apache.jena.query.QueryExecException ;
 import org.apache.jena.riot.WebContent ;
+import org.apache.jena.riot.web.HttpCaptureResponse;
 import org.apache.jena.riot.web.HttpOp ;
+import org.apache.jena.riot.web.HttpOp.CaptureInput;
 import org.apache.jena.shared.JenaException ;
 import org.apache.jena.sparql.util.Context;
+import org.apache.jena.web.HttpSC;
 import org.slf4j.Logger ;
 import org.slf4j.LoggerFactory ;
 
@@ -322,13 +327,13 @@ public class HttpQuery extends Params {
         try {
             try {
                 // Get the actual response stream
-                TypedInputStream stream = HttpOp.execHttpGet(target.toString(), contentTypeResult, client, getContext());
+                TypedInputStream stream = execHttpGet(target.toString(), contentTypeResult, client, getContext());
                 if (stream == null)
-                    throw new QueryExceptionHTTP(404);
+                    throw new QueryExceptionHTTP(NOT_FOUND_404, HttpSC.getMessage(NOT_FOUND_404));
                 return execCommon(stream);
             } catch (HttpException httpEx) {
                 // Back-off and try POST if something complain about long URIs
-                if (httpEx.getResponseCode() == 414)
+                if (httpEx.getStatusCode() == REQUEST_URI_TOO_LONG_414)
                     return execPost();
                 throw httpEx;
             }
@@ -336,6 +341,13 @@ public class HttpQuery extends Params {
             throw rewrap(httpEx);
         }
     }
+    
+    // With exception.
+    private static TypedInputStream execHttpGet(String url, String acceptHeader, HttpClient httpClient, HttpContext httpContext) {
+        HttpCaptureResponse<TypedInputStream> handler = new CaptureInput();
+        HttpOp.execHttpGet(url, acceptHeader, handler, httpClient, httpContext);
+        return handler.get();
+    }
 
     private InputStream execPost() throws QueryExceptionHTTP {
         URL target = null;
@@ -363,11 +375,11 @@ public class HttpQuery extends Params {
         // The historical contract of HTTP Queries has been to throw QueryExceptionHTTP however using the standard
         // ARQ HttpOp machinery we use these days means the internal HTTP errors come back as HttpException
         // Therefore we need to wrap appropriately
-        responseCode = httpEx.getResponseCode();
+        responseCode = httpEx.getStatusCode();
         if (responseCode != -1) {
         	// Was an actual HTTP error
         	String responseLine = httpEx.getStatusLine() != null ? httpEx.getStatusLine() : "No Status Line";
-        	return new QueryExceptionHTTP(responseCode, "HTTP " + responseCode + " error making the query: " + responseLine, httpEx);
+        	return new QueryExceptionHTTP(responseCode, responseLine, httpEx);
         } else if (httpEx.getMessage() != null) {
         	// Some non-HTTP error with a valid message e.g. Socket Communications failed, IO error
         	return new QueryExceptionHTTP(responseCode, "Unexpected error making the query: " + httpEx.getMessage(), httpEx);
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryExceptionHTTP.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryExceptionHTTP.java
index 0a36ca5..c601540 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryExceptionHTTP.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryExceptionHTTP.java
@@ -25,9 +25,8 @@ import org.apache.jena.query.QueryException ;
  *  Error codes are as HTTP status codes. */
 public class QueryExceptionHTTP extends QueryException
 {
-    private static final long serialVersionUID = 99L;  // Serializable.
-    public static final int noResponseCode = -1234 ;
-    private int responseCode = noResponseCode ;
+    public static final int noStatusCode = -1234 ;
+    private int statusCode = noStatusCode ;
     private final String responseMessage ;
     private String statusLine ;
     private String response;
@@ -44,7 +43,7 @@ public class QueryExceptionHTTP extends QueryException
     public QueryExceptionHTTP(int responseCode, String responseMessage)
     {
         super(responseMessage) ;
-        this.responseCode = responseCode ;
+        this.statusCode = responseCode ;
         this.responseMessage = responseMessage ;
     }
     
@@ -56,15 +55,21 @@ public class QueryExceptionHTTP extends QueryException
     public QueryExceptionHTTP(int responseCode)
     {
         super() ;
-        this.responseCode = responseCode ;
+        this.statusCode = responseCode ;
         this.responseMessage = null ;
     }
     
     /** The code for the reason for this exception
+     * @return statusCode
+     */  
+    public int getStatusCode() { return statusCode ; }
+
+    /** The code for the reason for this exception
      * @return responseCode
+     * @deprecated Use {@link #getStatusCode()}
      */  
-    public int getResponseCode() { return responseCode ; }
-    
+    @Deprecated
+    public int getResponseCode() { return getStatusCode(); }
     
     /** The message for the reason for this exception
      * @return message
@@ -88,20 +93,20 @@ public class QueryExceptionHTTP extends QueryException
     public QueryExceptionHTTP(Throwable cause)
     {
         super(cause);
-        this.responseCode = noResponseCode ;
+        this.statusCode = noStatusCode ;
         this.responseMessage = null ;
     }
     
     public QueryExceptionHTTP(String msg, Throwable cause)
     {
         super(msg, cause);
-        this.responseCode = noResponseCode ;
+        this.statusCode = noStatusCode ;
         this.responseMessage = msg ;
     }
     
     public QueryExceptionHTTP(int responseCode, String message, Throwable cause) {
         this(message, cause);
-        this.responseCode = responseCode;
+        this.statusCode = responseCode;
     }
 
     public QueryExceptionHTTP(int responseCode, String message, final HttpException ex) {
@@ -115,8 +120,8 @@ public class QueryExceptionHTTP extends QueryException
     {
         StringBuilder sb = new StringBuilder() ;
         sb.append("HttpException: ") ;
-        int code = getResponseCode() ;
-        if ( code != QueryExceptionHTTP.noResponseCode )
+        int code = getStatusCode() ;
+        if ( code != QueryExceptionHTTP.noStatusCode )
         {
             sb.append(code) ;
             if ( getResponseMessage() != null )
diff --git a/jena-arq/src/main/java/org/apache/jena/web/DatasetGraphAccessorHTTP.java b/jena-arq/src/main/java/org/apache/jena/web/DatasetGraphAccessorHTTP.java
index 32afa09..24c4f9c 100644
--- a/jena-arq/src/main/java/org/apache/jena/web/DatasetGraphAccessorHTTP.java
+++ b/jena-arq/src/main/java/org/apache/jena/web/DatasetGraphAccessorHTTP.java
@@ -115,7 +115,7 @@ public class DatasetGraphAccessorHTTP implements DatasetGraphAccessor {
         try {
             HttpOp.execHttpGet(url, graphAcceptHeader, graph, client, null) ;
         } catch (HttpException ex) {
-            if ( ex.getResponseCode() == HttpSC.NOT_FOUND_404 )
+            if ( ex.getStatusCode() == HttpSC.NOT_FOUND_404 )
                 return null ;
             throw ex ;
         }
@@ -137,7 +137,7 @@ public class DatasetGraphAccessorHTTP implements DatasetGraphAccessor {
             HttpOp.execHttpHead(url, WebContent.defaultGraphAcceptHeader, noResponse, client, null) ;
             return true ;
         } catch (HttpException ex) {
-            if ( ex.getResponseCode() == HttpSC.NOT_FOUND_404 )
+            if ( ex.getStatusCode() == HttpSC.NOT_FOUND_404 )
                 return false ;
             throw ex ;
         }
@@ -172,7 +172,7 @@ public class DatasetGraphAccessorHTTP implements DatasetGraphAccessor {
         try {
             HttpOp.execHttpDelete(url, noResponse, client, null) ;
         } catch (HttpException ex) {
-            if ( ex.getResponseCode() == HttpSC.NOT_FOUND_404 )
+            if ( ex.getStatusCode() == HttpSC.NOT_FOUND_404 )
                 return ;
         }
     }
diff --git a/jena-arq/src/main/java/org/apache/jena/web/HttpSC.java b/jena-arq/src/main/java/org/apache/jena/web/HttpSC.java
index 025c7bc..b15d6e5 100644
--- a/jena-arq/src/main/java/org/apache/jena/web/HttpSC.java
+++ b/jena-arq/src/main/java/org/apache/jena/web/HttpSC.java
@@ -694,13 +694,14 @@ public class HttpSC
         }
     }
 
-
     public enum Code
     {
         /*
          * --------------------------------------------------------------------
-         * Informational messages in 1xx series. As defined by ... RFC 1945 -
-         * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV 
+         * Informational messages in 1xx series. As defined by ...
+         * RFC 1945 - HTTP/1.0
+         * RFC 2616 - HTTP/1.1
+         * RFC 2518 - WebDAV 
          * and RFC2324
          */
 
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamUTF8.java b/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamUTF8.java
index 0b2347a..e45595b 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamUTF8.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamUTF8.java
@@ -29,59 +29,58 @@ import org.apache.jena.atlas.AtlasException ;
 public final class InStreamUTF8 extends Reader implements CharStream
 {
     // TODO Add line and col counts.
-    // See arq.utf8. 
+    // See arq.utf8.
     // TODO Better ready()/available() in InputStreamBuffered
-    
+    // TODO: chars > 16 bits -> convert to surrogate pairs.
+
     // The standard Java way of doing this is via charset decoders.
     // One small disadvantage is that bad UTF-8 does not get flagged as to
     // the byte position of the error.
-    
+
     // This class collects knowledge of how UTF-8 encoding works;
     // the Java classes are usually slightly faster compared to using
     // this class with an InputStreamBuffered but the difference is small.
     // This class generated meaningful error messages (when line/col added).
-    
+
     // The Java classes copy-convert a byte buffer into a char buffer.
     // Sometimes, for example in a parser, this isn't a convenient model
     // because the app is looking one character at a time and accumulating
     // the chars until it sees the end of a token of arbitrary length
-    // or processes escape sequences.  
+    // or processes escape sequences.
     //
     // The app might use a StringBuilder so the bytes get copied into
     // a char buffer and out again.  Instead, this code assumes the
     // app is in charge of that.
-    
-    // UTF-8 (UTF-16) is different from other character sets because 
+
+    // UTF-8 (UTF-16) is different from other character sets because
     // the relationship with Java's internal character representation is
-    // arithmetic, not a character mapping. 
-    
-    // Todo: chars > 16 bits -> surrogate pairs. 
-    
+    // arithmetic, not a character mapping.
+
     /*
      * http://en.wikipedia.org/wiki/UTF-8
      * http://tools.ietf.org/html/rfc3629
      * http://www.ietf.org/rfc/rfc3629.txt
-     * 
+     *
      * Unicode                                  Byte1       Byte2       Byte3       Byte4
      * U+0000–U+007F    0 to 127                0xxxxxxx
-     * U+0080–U+07FF    128 to 2,047            110yyyxx    10xxxxxx 
+     * U+0080–U+07FF    128 to 2,047            110yyyxx    10xxxxxx
      * U+0800–U+FFFF    2,048 to 65,535         1110yyyy    10yyyyxx    10xxxxxx
      * U+10000–U+10FFFF 65,536 to 1,114,111     11110zzz    10zzyyyy    10yyyyxx    10xxxxxx
-     * 
+     *
      * Restricted cases (RFC 3629)
      * 11110101-11110111    F5-F7   245-247     start of 4-byte sequence for codepoint above 10FFFF
      * 11111000-11111011    F8-FB   248-251     start of 5-byte sequence
      * 11111100-11111101    FC-FD   252-253     start of 6-byte sequence
-     * 
+     *
      * Illegal:
      * 11000000-11000001    C0-C1   192-193     Overlong encoding: start of a 2-byte sequence, but code point <= 127
      * 11111110-11111111    FE-FF   254-255     Invalid: not defined by original UTF-8 specification
      */
-    
-    // There is some sort of stream decoder backing the Sun implementation 
+
+    // There is some sort of stream decoder backing the Sun implementation
     // of CharsetDecoder (sun.io.StreamDecoder) but it's not on all platforms
     // I want a known decoder specifically for UTF8
-    
+
     private InputStreamBuffered input ;
     //private long count = 0 ;
 
@@ -94,20 +93,19 @@ public final class InStreamUTF8 extends Reader implements CharStream
         }
         input = new InputStreamBuffered(in) ;
     }
-    
+
     public InStreamUTF8(InputStreamBuffered in) { input = in ; }
-    
 
     @Override
     public boolean ready() throws IOException
     {
         return input.available() > 0 ;
     }
-    
+
     @Override
     public void close() throws IOException
     { input.close() ; }
-    
+
     @Override
     public void closeStream()
     { IO.close(input) ; }
@@ -133,26 +131,26 @@ public final class InStreamUTF8 extends Reader implements CharStream
         // if ( ! Character.isDefined(ch) ) throw new
         // AtlasException(String.format("Undefined codepoint: 0x%04X", ch)) ;
         return ch;
-    }    
-    
+    }
+
     /** Next codepoint, given the first byte of any UTF-8 byte sequence is already known.
      *  Not necessarily a valid char (this function can be used a straight UTF8 decoder
      */
     @Override
     public final int advance()
     { return advance(input) ; }
-    
+
     /** Next codepoint */
     public static final int advance(InputStreamBuffered input) {
         int x = input.advance() ;
         if ( x == -1 ) return -1 ;
         return advance(input, x) ;
     }
-    
+
     /** Next codepoint, given the first byte of any UTF-8 byte sequence is already known.
      * Not necessarily a valid char (this function can be used as a straight UTF8 decoder).
      */
-    
+
     private static final int advance(InputStreamBuffered input, int x) {
         //count++ ;
         // ASCII Fastpath
@@ -199,19 +197,19 @@ public final class InStreamUTF8 extends Reader implements CharStream
             throw new AtlasException(String.format("Undefined codepoint: 0x%04X", ch));
         return ch;
     }
-    
+
     private static int readMultiBytes(InputStreamBuffered input, int start, int len) {
         int x = start ;
         for ( int i = 0 ; i < len-1 ; i++ ) {
             int x2 = input.advance() ;
             if ( x2 == -1 )
                 throw new AtlasException("Premature end to UTF-8 sequence at end of input") ;
-            
+
             if ( (x2 & 0xC0) != 0x80 )
                 //throw new AtlasException("Illegal UTF-8 processing character "+count+": "+x2) ;
                 throw new AtlasException(String.format("Illegal UTF-8 processing character: 0x%04X",x2)) ;
             // 6 bits of x2
-            x = (x << 6) | (x2 & 0x3F); 
+            x = (x << 6) | (x2 & 0x3F);
         }
         return x ;
     }
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/OutStreamUTF8.java b/jena-base/src/main/java/org/apache/jena/atlas/io/OutStreamUTF8.java
index 36c078f..fbafce0 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/io/OutStreamUTF8.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/OutStreamUTF8.java
@@ -24,9 +24,9 @@ import java.io.Writer ;
 
 /** Output UTF-8 encoded data.
  *  This class implements the "Modified UTF8" encoding rules (null {@literal ->} C0 80)
- *  It will encode any 16 bit value.  
- *  It can be used as a pure UTf-8 encoder. 
- * 
+ *  It will encode any 16 bit value.
+ *  It can be used as a pure UTF-8 encoder.
+ *
  *  @see InStreamUTF8
  */
 public final class OutStreamUTF8 extends Writer
@@ -38,26 +38,26 @@ public final class OutStreamUTF8 extends Writer
         // Buffer?
         this.out = out ;
     }
-    
+
     @Override
     public void write(char[] cbuf, int off, int len) throws IOException
     {
         for ( int i = 0 ; i < len; i++ )
             write(cbuf[off+i]) ;
     }
-    
+
     @Override
     public void write(int ch) throws IOException
     { output(out, ch) ; }
-    
+
     @Override
     public void write(char[] b) throws IOException
     { write(b, 0, b.length) ; }
-    
+
     @Override
     public void write(String str) throws IOException
     { write(str,0, str.length()) ; }
-    
+
     @Override
     public void write(String str, int idx, int len) throws IOException
     {
@@ -66,15 +66,15 @@ public final class OutStreamUTF8 extends Writer
     }
 
     public void output(int x)
-    { 
-        try { 
+    {
+        try {
             output(out, x) ;
         } catch (IOException ex) { IO.exception(ex) ; }
     }
-    
+
     /*
-     * Bits 
-     * 7    U+007F      1 to 127              0xxxxxxx 
+     * Bits
+     * 7    U+007F      1 to 127              0xxxxxxx
      * 11   U+07FF      128 to 2,047          110xxxxx 10xxxxxx
      * 16   U+FFFF      2,048 to 65,535       1110xxxx 10xxxxxx 10xxxxxx
      * 21   U+1FFFFF    65,536 to 1,114,111   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
@@ -89,7 +89,7 @@ public final class OutStreamUTF8 extends Writer
             out.write(ch) ;
             return ;
         }
-        
+
         if ( ch == 0 )
         {
             // Modified UTF-8.
@@ -97,14 +97,14 @@ public final class OutStreamUTF8 extends Writer
             out.write(0x80) ;
             return ;
         }
-        
-        // Better? output(int HiMask, int byte length, int value) 
-        
+
+        // Better? output(int HiMask, int byte length, int value)
+
         if ( ch <= 0x07FF )
         {
             // 11 bits : 110yyyyy 10xxxxxx
             // int x1 = ( ((ch>>(11-5))&0x7) | 0xC0 ) ; outputBytes(out, x1, 2, ch) ; return ;
-            int x1 = ( ((ch>>(11-5))&0x01F ) | 0xC0 ) ; 
+            int x1 = ( ((ch>>(11-5))&0x01F ) | 0xC0 ) ;
             int x2 = ( (ch&0x3F)  | 0x80 ) ;
             out.write(x1) ;
             out.write(x2) ;
@@ -122,12 +122,12 @@ public final class OutStreamUTF8 extends Writer
             out.write(x3) ;
             return ;
         }
-        
+
 //        if ( Character.isDefined(ch) )
 //            throw new AtlasException("not a character") ;
-        
+
         //if ( true ) throw new InternalErrorException("Valid code point for Java but not encodable") ;
-        
+
         // Not java, where chars are 16 bit.
         if ( ch <= 0x1FFFFF )
         {
@@ -152,15 +152,25 @@ public final class OutStreamUTF8 extends Writer
             return ;
         }
     }
-    
+
+    /**
+     * Write a total of {@code byteLength bytes}; first, value {@code x1}, then the
+     * remaining bytes of {@code ch}.
+     * <pre>
+     *   byte x1
+     *   10xxxxxx
+     *   10xxxxxx
+     *   ...
+     * </pre>
+     */
     private static void outputBytes(OutputStream out, int x1, int byteLength, int ch) throws IOException
     {
-        // ByteLength = 3 => 2 byteLenth => shift=6 and shift=0  
+        // ByteLength = 3 => 2 byteLength => shift=6 and shift=0
         out.write(x1) ;
         byteLength-- ; // remaining bytes
         for ( int i = 0 ; i < byteLength ; i++ )
         {
-            // 6 Bits, loop from high to low  
+            // 6 Bits, loop from high to low
             int shift = 6*(byteLength-i-1) ;
             int x =  (ch>>shift) & 0x3F ;
             x = x | 0x80 ;  // 10xxxxxx
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Registry.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Registry.java
index 5288844..b2d825c 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/Registry.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Registry.java
@@ -34,7 +34,6 @@ public class Registry<K,T>
     public boolean isRegistered(K key)  { return registry.containsKey(key) ; }
     public void remove(K key)           { registry.remove(key) ; } 
     public Collection<K> keys()         { return registry.keySet() ; }
-    //public Iterator<String> keys()      { return registry.keySet().iterator() ; }
     public int size()                   { return registry.size() ; }
     public boolean isEmpty()            { return registry.isEmpty() ; }
     public void clear()                 { registry.clear() ; }
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_AllowGET.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_AllowGET.java
new file mode 100644
index 0000000..0d54c52
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_AllowGET.java
@@ -0,0 +1,74 @@
+/*
+ * 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.access;
+
+import static org.apache.jena.riot.web.HttpNames.*;
+
+import java.util.function.Function;
+
+import org.apache.jena.atlas.lib.InternalErrorException;
+import org.apache.jena.fuseki.servlets.ActionService;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.ServletOps;
+
+/**
+ * Wrapper for an {@link ActionService} that rejects a request with a "write" HTTP
+ * method to an graph-access-controlled dataset. Graph-access-controlled dataset only
+ * support read (query and GSP GET).
+ */
+public class AccessCtl_AllowGET extends ActionService {
+
+    private final ActionService other;
+    private final Function<HttpAction, String> requestUser;
+    private final String label;
+
+    public AccessCtl_AllowGET(ActionService other, String label,
+                              Function<HttpAction, String> determineUser) {
+        this.other = other;
+        this.label = label;
+        this.requestUser = determineUser;
+    }
+
+    @Override
+    public void validate(HttpAction action) {
+        String requestMethod = action.getMethod();
+        switch ( requestMethod ) {
+            case METHOD_GET:
+            case METHOD_HEAD:
+            case METHOD_OPTIONS:
+                break;
+            case METHOD_DELETE:
+            case METHOD_PATCH:
+            case METHOD_POST:
+            case METHOD_PUT:
+            case METHOD_TRACE: {
+                if ( label == null )
+                    ServletOps.errorBadRequest("Not supported");
+                ServletOps.errorBadRequest(label+" : not supported");
+                throw new InternalErrorException("AccessCtl_DenyUpdate: "+ "didn't reject request");
+            }
+        }
+        other.validate(action);
+    }
+
+    @Override
+    public void execute(HttpAction action) {
+        other.execute(action);
+    }
+}
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_Update.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_DenyUpdate.java
similarity index 50%
rename from jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_Update.java
rename to jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_DenyUpdate.java
index 5af9988..301c037 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_Update.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_DenyUpdate.java
@@ -18,52 +18,46 @@
 
 package org.apache.jena.fuseki.access;
 
-import java.io.InputStream;
 import java.util.function.Function;
 
+import org.apache.jena.atlas.lib.InternalErrorException;
 import org.apache.jena.fuseki.servlets.ActionService;
 import org.apache.jena.fuseki.servlets.HttpAction;
-import org.apache.jena.fuseki.servlets.SPARQL_Update;
 import org.apache.jena.fuseki.servlets.ServletOps;
 import org.apache.jena.sparql.core.DatasetGraph;
 
-/** An Update {@link ActionService} that denies SPARQL Update in access controlled datasets. */
-final
-public class AccessCtl_SPARQL_Update extends SPARQL_Update {
+/**
+ * Wrapper for an {@link ActionService} that rejects a request to a graph-access-controlled dataset.
+ * Typically, that's one of the update operations -
+ * Graph-access-controlled dataset only support read (query and GSP GET).
+ */
+public class AccessCtl_DenyUpdate extends ActionService {
+
+    private final ActionService other;
     private final Function<HttpAction, String> requestUser;
+    private final String label;
 
-    public AccessCtl_SPARQL_Update(Function<HttpAction, String> requestUser) {
-        this.requestUser = requestUser; 
+    public AccessCtl_DenyUpdate(ActionService other, String label,
+                                Function<HttpAction, String> determineUser) {
+        this.other = other;
+        this.label = label;
+        this.requestUser = determineUser;
     }
 
     @Override
-    protected void validate(HttpAction action) {
-        super.validate(action);
-        
+    public void validate(HttpAction action) {
         DatasetGraph dsg = action.getDataset();
-        if ( ! DataAccessCtl.isAccessControlled(dsg) )
-            return;
-        ServletOps.errorBadRequest("SPARQL Update not supported");
-    }
-    
-    @Override
-    protected void perform(HttpAction action) {
-        DatasetGraph dsg = action.getDataset() ;
-        if ( ! DataAccessCtl.isAccessControlled(dsg) ) {
-            super.perform(action);
-            return;
+        if ( DataAccessCtl.isAccessControlled(dsg) ) {
+            if ( label == null )
+                ServletOps.errorBadRequest("Not supported");
+            ServletOps.errorBadRequest(label+" : not supported");
+            throw new InternalErrorException("AccessCtl_DenyUpdate: "+ "didn't reject request");
         }
-        ServletOps.errorBadRequest("SPARQL Update not supported");
+        other.validate(action);
     }
-    
+
     @Override
-    protected void execute(HttpAction action, InputStream input) {
-        DatasetGraph dsg = action.getDataset() ;
-        if ( ! DataAccessCtl.isAccessControlled(dsg) ) {
-            super.execute(action, input);
-            return;
-        }
-        ServletOps.errorBadRequest("SPARQL Update not supported");
+    public void execute(HttpAction action) {
+        other.execute(action);
     }
 }
-
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_GSP_R.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_GSP_R.java
similarity index 86%
rename from jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_GSP_R.java
rename to jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_GSP_R.java
index cc7a5de..ff28014 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_GSP_R.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_GSP_R.java
@@ -20,15 +20,15 @@ package org.apache.jena.fuseki.access;
 
 import java.util.function.Function;
 
+import org.apache.jena.fuseki.servlets.GSP_R;
 import org.apache.jena.fuseki.servlets.HttpAction;
-import org.apache.jena.fuseki.servlets.SPARQL_GSP_R;
 import org.apache.jena.sparql.core.DatasetGraph;
 
-public class AccessCtl_SPARQL_GSP_R extends SPARQL_GSP_R {
-    
+public class AccessCtl_GSP_R extends GSP_R {
+
     private final Function<HttpAction, String> requestUser;
 
-    public AccessCtl_SPARQL_GSP_R(Function<HttpAction, String> determineUser) {
+    public AccessCtl_GSP_R(Function<HttpAction, String> determineUser) {
         this.requestUser = determineUser;
     }
 
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_REST_Quads_R.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_REST_Quads_R.java
deleted file mode 100644
index d96807a..0000000
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_REST_Quads_R.java
+++ /dev/null
@@ -1,43 +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.access;
-
-import java.util.function.Function;
-
-import org.apache.jena.fuseki.servlets.HttpAction;
-import org.apache.jena.fuseki.servlets.REST_Quads_R;
-import org.apache.jena.sparql.core.DatasetGraph;
-
-/**
- * Filter for {@link REST_Quads_R} that inserts a security filter on read-access to the
- * {@link DatasetGraph}.
- */
-public class AccessCtl_REST_Quads_R extends REST_Quads_R {
-    
-    private final Function<HttpAction, String> requestUser;
-    
-    public AccessCtl_REST_Quads_R(Function<HttpAction, String> determineUser) {
-        this.requestUser = determineUser; 
-    }
-
-    @Override
-    protected DatasetGraph decideDataset(HttpAction action) {
-        return DataAccessLib.decideDataset(action, requestUser);
-    }
-}
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_REST_Quads_RW.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_REST_Quads_RW.java
deleted file mode 100644
index b36765c..0000000
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_REST_Quads_RW.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.access;
-
-import java.util.function.Function;
-
-import org.apache.jena.fuseki.servlets.HttpAction;
-import org.apache.jena.fuseki.servlets.REST_Quads_R;
-import org.apache.jena.fuseki.servlets.REST_Quads_RW;
-import org.apache.jena.fuseki.servlets.ServletOps;
-import org.apache.jena.sparql.core.DatasetGraph;
-
-/**
- * Filter for {@link REST_Quads_R} that inserts a security filter on read-access to the
- * {@link DatasetGraph}.
- */
-public class AccessCtl_REST_Quads_RW extends REST_Quads_RW {
-    
-    private final Function<HttpAction, String> requestUser;
-    
-    public AccessCtl_REST_Quads_RW(Function<HttpAction, String> determineUser) {
-        this.requestUser = determineUser; 
-    }
-
-    @Override
-    protected void validate(HttpAction action) {
-        super.validate(action);
-        DatasetGraph dsg = action.getDataset();
-        if ( ! DataAccessCtl.isAccessControlled(dsg) )
-            return;
-        ServletOps.errorBadRequest("REST update of the dataset not supported");
-    }
-}
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_GSP_RW.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_GSP_RW.java
deleted file mode 100644
index ce95290..0000000
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_GSP_RW.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.access;
-
-import java.util.function.Function;
-
-import org.apache.jena.fuseki.servlets.HttpAction;
-import org.apache.jena.fuseki.servlets.REST_Quads_R;
-import org.apache.jena.fuseki.servlets.SPARQL_GSP_RW;
-import org.apache.jena.fuseki.servlets.ServletOps;
-import org.apache.jena.sparql.core.DatasetGraph;
-
-/**
- * Filter for {@link REST_Quads_R} that inserts a security filter on read-access to the
- * {@link DatasetGraph}.
- */
-public class AccessCtl_SPARQL_GSP_RW extends SPARQL_GSP_RW {
-    
-    private final Function<HttpAction, String> requestUser;
-    
-    public AccessCtl_SPARQL_GSP_RW(Function<HttpAction, String> determineUser) {
-        this.requestUser = determineUser; 
-    }
-
-    @Override
-    protected void validate(HttpAction action) {
-        super.validate(action);
-        DatasetGraph dsg = action.getDataset();
-        if ( ! DataAccessCtl.isAccessControlled(dsg) )
-            return;
-        ServletOps.errorBadRequest("SPARQL Graph Store Protocol update not supported");
-    }
-}
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_QueryDataset.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_QueryDataset.java
index 54948a9..19d54e7 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_QueryDataset.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_QueryDataset.java
@@ -26,10 +26,7 @@ import java.util.List;
 import java.util.function.Function;
 
 import org.apache.jena.atlas.lib.Pair;
-import org.apache.jena.fuseki.servlets.ActionService;
-import org.apache.jena.fuseki.servlets.HttpAction;
-import org.apache.jena.fuseki.servlets.SPARQL_QueryDataset;
-import org.apache.jena.fuseki.servlets.ServletOps;
+import org.apache.jena.fuseki.servlets.*;
 import org.apache.jena.query.Query;
 import org.apache.jena.query.QueryExecution;
 import org.apache.jena.sparql.core.*;
@@ -41,10 +38,10 @@ public class AccessCtl_SPARQL_QueryDataset extends SPARQL_QueryDataset {
     private final Function<HttpAction, String> requestUser;
 
     public AccessCtl_SPARQL_QueryDataset(Function<HttpAction, String> requestUser) {
-        this.requestUser = requestUser; 
+        this.requestUser = requestUser;
     }
 
-    private static boolean ALLOW_FROM = true; 
+    private static boolean ALLOW_FROM = true;
 
     @Override
     protected Collection<String> customParams() {
@@ -61,7 +58,7 @@ public class AccessCtl_SPARQL_QueryDataset extends SPARQL_QueryDataset {
         if ( ! DataAccessCtl.isAccessControlled(dsg) )
             return super.decideDataset(action, query, queryStringLog);
 
-        DatasetDescription dsDesc0 = getDatasetDescription(action, query);
+        DatasetDescription dsDesc0 = SPARQLProtocol.getDatasetDescription(action, query);
         SecurityContext sCxt = DataAccessLib.getSecurityContext(action, dsg, requestUser);
         DatasetGraph dsg2 = dynamicDataset(action, query, dsg, dsDesc0, sCxt);
         return Pair.create(dsg2,  query);
@@ -72,7 +69,7 @@ public class AccessCtl_SPARQL_QueryDataset extends SPARQL_QueryDataset {
             return dsg0;
         if ( ! ALLOW_FROM )
             ServletOps.errorBadRequest("Use GRAPH. (FROM/FROM NAMED is not compatible with data access control.)");
-        
+
         DatasetDescription dsDesc1 = DatasetDescription.create(
             mask(dsDesc0.getDefaultGraphURIs(), sCxt),
             mask(dsDesc0.getNamedGraphURIs(),   sCxt));
@@ -85,26 +82,26 @@ public class AccessCtl_SPARQL_QueryDataset extends SPARQL_QueryDataset {
             dsDesc1.getDefaultGraphURIs().remove(Quad.unionGraph.getURI());
             dsDesc1.getDefaultGraphURIs().addAll(sCxt.visibleGraphNames());
         }
-        
-        DatasetGraph dsg1 = DynamicDatasets.dynamicDataset(dsDesc1, dsg0, false) ;
+
+        DatasetGraph dsg1 = DynamicDatasets.dynamicDataset(dsDesc1, dsg0, false);
         if ( query.hasDatasetDescription() ) {
-             query.getGraphURIs().clear() ;
-             query.getNamedGraphURIs().clear() ;
+             query.getGraphURIs().clear();
+             query.getNamedGraphURIs().clear();
         }
-        return dsg1 ;
+        return dsg1;
     }
 
     // Pass only those graphURIs in the security context.
     private List<String> mask(List<String> graphURIs, SecurityContext sCxt) {
         Collection<String> names = sCxt.visibleGraphNames();
         if ( names == null )
-            return graphURIs; 
+            return graphURIs;
         return graphURIs.stream()
             .filter(gn->names.contains(gn)
                         || ( sCxt.visableDefaultGraph() && Quad.defaultGraphIRI.getURI().equals(gn))
                         || ( Quad.unionGraph.getURI().equals(gn) )
                         )
-            .collect(toList()) ;
+            .collect(toList());
     }
 
     @Override
@@ -115,7 +112,7 @@ public class AccessCtl_SPARQL_QueryDataset extends SPARQL_QueryDataset {
                 // but specialised setups might have DynamicDatasetGraph as the base dataset.
                 ServletOps.errorBadRequest("FROM/FROM NAMED is not compatible with data access control.");
         }
-        
+
         // Dataset of the service, not computed by decideDataset.
         DatasetGraph dsg = action.getActiveDSG();
         if ( dsg == null )
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_Upload.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_Upload.java
deleted file mode 100644
index c4e926a..0000000
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_Upload.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.access;
-
-import java.util.function.Function;
-
-import org.apache.jena.fuseki.servlets.HttpAction;
-import org.apache.jena.fuseki.servlets.REST_Quads_R;
-import org.apache.jena.fuseki.servlets.SPARQL_Upload;
-import org.apache.jena.fuseki.servlets.ServletOps;
-import org.apache.jena.sparql.core.DatasetGraph;
-
-/**
- * Filter for {@link REST_Quads_R} that inserts a security filter on read-access to the
- * {@link DatasetGraph}.
- */
-public class AccessCtl_SPARQL_Upload extends SPARQL_Upload {
-    
-    private final Function<HttpAction, String> requestUser;
-    
-    public AccessCtl_SPARQL_Upload(Function<HttpAction, String> determineUser) {
-        this.requestUser = determineUser; 
-    }
-
-    @Override
-    protected void validate(HttpAction action) {
-        super.validate(action);
-        DatasetGraph dsg = action.getDataset();
-        if ( ! DataAccessCtl.isAccessControlled(dsg) )
-            return;
-        ServletOps.errorBadRequest("Upload not supported");
-    }
-}
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java
index 67ccec1..71363bc 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java
@@ -30,35 +30,35 @@ import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.util.graph.GraphUtils;
 
 public class AssemblerAccessDataset extends AssemblerBase {
-    
+
     /*
-     * <#access_dataset>  rdf:type access:AccessControlledDataset ;
-     *    access:registry   <#securityRegistry> ;
-     *    access:dataset    <#tdb_dataset_read> ;
+     * <#access_dataset>  rdf:type access:AccessControlledDataset;
+     *    access:registry   <#securityRegistry>;
+     *    access:dataset    <#tdb_dataset_read>;
      *    .
      */
     @Override
     public Dataset open(Assembler a, Resource root, Mode mode) {
         if ( ! GraphUtils.exactlyOneProperty(root, VocabSecurity.pSecurityRegistry) )
-            throw new AssemblerException(root, "Expected exactly one access:registry property"); 
+            throw new AssemblerException(root, "Expected exactly one access:registry property");
         if ( ! GraphUtils.exactlyOneProperty(root, VocabSecurity.pDataset) )
-            throw new AssemblerException(root, "Expected exactly one access:dataset property"); 
-        
+            throw new AssemblerException(root, "Expected exactly one access:dataset property");
+
         RDFNode rnRegistry = root.getProperty(VocabSecurity.pSecurityRegistry).getObject();
         RDFNode rnDataset = root.getProperty(VocabSecurity.pDataset).getObject();
-        
-        AuthorizationService sr = (AuthorizationService)a.open(rnRegistry.asResource()) ;
+
+        AuthorizationService sr = (AuthorizationService)a.open(rnRegistry.asResource());
         DatasetGraph dsgBase = ((Dataset)a.open(rnDataset.asResource())).asDatasetGraph();
-        
+
         DatasetGraph dsg = new DatasetGraphAccessControl(dsgBase, sr);
-        
+
         // Add marker
         // ds.getContext().set(DataAccessCtl.symControlledAccess, true);
-        
+
         // Add security registry : if this dataset is wrapped then this means the AuthorizationService is still accessible.
-        // But adding to DatasetGraphAccessControl (currently) pushes it down to the wrapped base DSG. 
+        // But adding to DatasetGraphAccessControl (currently) pushes it down to the wrapped base DSG.
         //dsg.getContext().set(DataAccessCtl.symAuthorizationService, sr);
         return DatasetFactory.wrap(dsg);
     }
-    
+
 }
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerSecurityRegistry.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerSecurityRegistry.java
index c0ef33c..156f6c7 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerSecurityRegistry.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerSecurityRegistry.java
@@ -44,20 +44,20 @@ import org.apache.jena.sparql.util.graph.GraphList;
 import org.apache.jena.sparql.util.graph.GraphUtils;
 
 public class AssemblerSecurityRegistry extends AssemblerBase {
-    
+
     /**
      * SecurityRegistry.
-     * Builds a SecurityRegistry - a map fron user name to 
-     * 
-     * <#securityRegistry> rdf:type access:SecurityRegistry ;
-     *    access:entry ("user1" <http://host/graphname1>  <http://host/graphname2> ) ;) ;
-     *    access:entry ("user1" <http://host/graphname3> ) ;
-     *    access:entry ("user2" <http://host/graphname3> ) ;
-     *    
+     * Builds a SecurityRegistry - a map fron user name to
+     *
+     * <#securityRegistry> rdf:type access:SecurityRegistry;
+     *    access:entry ("user1" <http://host/graphname1>  <http://host/graphname2> ););
+     *    access:entry ("user1" <http://host/graphname3> );
+     *    access:entry ("user2" <http://host/graphname3> );
+     *
      *    .
-     * 
+     *
      * ## Drop?
-     * access:entry [ :user "user2" ; :graphs (<http://host/graphname3> ) ] ;
+     * access:entry [ :user "user2"; :graphs (<http://host/graphname3> ) ];
      */
 
     @Override
@@ -68,33 +68,33 @@ public class AssemblerSecurityRegistry extends AssemblerBase {
         if ( ! sIter.hasNext() )
             throw new AssemblerException(root, "No access entries");
         Multimap<String, Node> map = ArrayListMultimap.create();
-        
+
         sIter.forEachRemaining(s->{
-            RDFNode n = s.getObject(); 
+            RDFNode n = s.getObject();
             if ( ! n.isResource())
                 throw new AssemblerException(root, "Found access:entry with non-resource");
-                
+
             Resource r = (Resource)n;
             GNode entry = new GNode(root.getModel().getGraph(), n.asNode());
             if ( GraphList.isListNode(entry) ) {
-                // Format:: access:entry ("user1" <http://host/graphname1>  <http://host/graphname2> ) ;
+                // Format:: access:entry ("user1" <http://host/graphname1>  <http://host/graphname2> );
                 parseList(map, root, entry);
             } else if ( r.hasProperty(VocabSecurity.pUser) || r.hasProperty(VocabSecurity.pGraphs) ) {
-                // Format:: access:entry [ :user "user2" ; :graphs (<http://host/graphname3> ) ]
+                // Format:: access:entry [ :user "user2"; :graphs (<http://host/graphname3> ) ]
                 parseStruct(map, root, r);
             } else
                 throw new AssemblerException(root, "Found access:entry but failed to parse the object: "+s.getSubject());
         });
-        
+
         map.keySet().forEach(u->{
             SecurityContext sCxt = new SecurityContextView(map.get(u));
             registry.put(u, sCxt);
         });
-        
+
         return registry;
     }
 
-    /** Format:: access:entry ("user1" <http://host/graphname1>  <http://host/graphname2> ) ; */
+    /** Format:: access:entry ("user1" <http://host/graphname1>  <http://host/graphname2> ); */
     private void parseList(Multimap<String, Node> map, Resource root, GNode entry) {
         List<Node> members = GraphList.members(entry);
         // string, then URIs.
@@ -108,13 +108,13 @@ public class AssemblerSecurityRegistry extends AssemblerBase {
         accessEntries(root, map, user, graphs);
     }
 
-    /** Format:: access:entry [ :user "user2" ; :graphs (<http://host/graphname3> ) ] */
+    /** Format:: access:entry [ :user "user2"; :graphs (<http://host/graphname3> ) ] */
     private void parseStruct(Multimap<String, Node> map, Resource root, Resource r) {
         if ( ! GraphUtils.exactlyOneProperty(r, VocabSecurity.pUser) )
-            throw new AssemblerException(root, "Expected exactly one access:user property for "+r); 
+            throw new AssemblerException(root, "Expected exactly one access:user property for "+r);
         if ( ! GraphUtils.exactlyOneProperty(r, VocabSecurity.pGraphs) )
-            throw new AssemblerException(root, "Expected exactly one access:graphs property for "+r); 
-        
+            throw new AssemblerException(root, "Expected exactly one access:graphs property for "+r);
+
         String user = GraphUtils.getStringValue(r, VocabSecurity.pUser);
         r.listProperties(VocabSecurity.pGraphs).mapWith(s->s.getObject()).forEachRemaining(x->{
             List<Node> graphs = new ArrayList<>();
@@ -131,7 +131,7 @@ public class AssemblerSecurityRegistry extends AssemblerBase {
             accessEntries(root, map, user, graphs);
         });
     }
-    
+
     private Node graphLabel(Node x, Resource root) {
         if ( SecurityContext.allGraphsStr.equals(x) ) x = SecurityContext.allGraphs;
         if ( SecurityContext.allNamedGraphsStr.equals(x) ) x = SecurityContext.allNamedGraphs;
@@ -139,12 +139,12 @@ public class AssemblerSecurityRegistry extends AssemblerBase {
             throw new AssemblerException(root, "Not a graph name: "+x);
         return x;
     }
-    
+
     // Unfinished.
-    private final static boolean SKIP_ALLGRAPH = true; 
-    
+    private final static boolean SKIP_ALLGRAPH = true;
+
     private void accessEntries(Resource root, Multimap<String, Node> map, String user, List<Node> _graphs) {
-        // Convert string names for graphs to URIs. 
+        // Convert string names for graphs to URIs.
         Set<Node> graphs = _graphs.stream().map(n->graphLabel(n, root)).collect(Collectors.toSet());
 
         if ( graphs.contains(SecurityContext.allGraphs) ) {
@@ -156,29 +156,29 @@ public class AssemblerSecurityRegistry extends AssemblerBase {
             boolean dft = dftPresent(graphs);
             Node x = SecurityContext.allNamedGraphs;
             if ( dft )
-                // Put in "*" instead. 
+                // Put in "*" instead.
                 x = SecurityContext.allGraphs;
             map.removeAll(user);
             map.put(user, x);
             return;
         }
-        
+
         if ( SKIP_ALLGRAPH ) {
             if ( graphs.contains(SecurityContext.allGraphs) ) {
-                Log.warn(this, "Graph name '"+SecurityContext.allGraphsStr+"' not supported yet"); 
+                Log.warn(this, "Graph name '"+SecurityContext.allGraphsStr+"' not supported yet");
                 graphs.remove(SecurityContext.allGraphs);
             }
             if ( graphs.contains(SecurityContext.allNamedGraphs) ) {
-                Log.warn(this, "Graph name '"+SecurityContext.allNamedGraphsStr+"' not supported yet"); 
+                Log.warn(this, "Graph name '"+SecurityContext.allNamedGraphsStr+"' not supported yet");
                 graphs.remove(SecurityContext.allNamedGraphs);
             }
         }
-        
+
         map.putAll(user, graphs);
     }
 
     private boolean dftPresent(Collection<Node> nodes) {
         return nodes.stream().anyMatch(n->Quad.isDefaultGraph(n));
     }
-    
+
 }
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AuthorizationService.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AuthorizationService.java
index dd8e7c6..fa262f5 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AuthorizationService.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AuthorizationService.java
@@ -22,7 +22,7 @@ import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * A {@link AuthorizationService} implemented with a {@link ConcurrentHashMap}.
- */ 
+ */
 public interface AuthorizationService {
 
     /** Return the security context  for a geiven actor (user) */
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java
index d49e2ab..4eff68b 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java
@@ -35,17 +35,17 @@ import org.apache.jena.sparql.util.Context;
 import org.apache.jena.sparql.util.Symbol;
 import org.apache.jena.sys.JenaSystem;
 
-/** A library of operations related to data access security for Fuseki */  
+/** A library of operations related to data access security for Fuseki */
 public class DataAccessCtl {
     static { JenaSystem.init(); }
-    
+
 //    /**
 //     * Flag for whether this is data access controlled or not - boolean false or undef for "not
 //     * controlled". This is an alternative to {@link DatasetGraphAccessControl}.
 //     * @see #isAccessControlled(DatasetGraph)
 //     */
 //    public static final Symbol   symControlledAccess        = Symbol.create(VocabSecurity.getURI() + "controlled");
-    
+
     /**
      * Symbol for the {@link AuthorizationService}.
      * This is an alternative to {@link DatasetGraphAccessControl}.
@@ -53,7 +53,7 @@ public class DataAccessCtl {
      */
     public static final Symbol   symAuthorizationService    = Symbol.create(VocabSecurity.getURI() + "authService");
 
-    /** Get the user from the servlet context via {@link HttpServletRequest#getRemoteUser} */ 
+    /** Get the user from the servlet context via {@link HttpServletRequest#getRemoteUser} */
     public static final Function<HttpAction, String> requestUserServlet = (action)->action.getUser();
 
     /**
@@ -72,14 +72,14 @@ public class DataAccessCtl {
     }
 
     /**
-     * Return a {@link DatasetGraph} with added data access control. 
+     * Return a {@link DatasetGraph} with added data access control.
      * Use of the original {@code DatasetGraph} is not controlled.
      */
     public static Dataset controlledDataset(Dataset dsBase, AuthorizationService reg) {
         DatasetGraph dsg = controlledDataset(dsBase.asDatasetGraph(), reg);
         return DatasetFactory.wrap(dsg);
     }
-    
+
     /**
      * Return a {@link DatasetGraph} with added data access control. Use of the original
      * {@code DatasetGraph} is not controlled.
@@ -91,10 +91,10 @@ public class DataAccessCtl {
                 return dsgx;
             throw new IllegalArgumentException("DatasetGraph is alerady wrapped on a DatasetGraphAccessControl with a different AuthorizationService");
         }
-        
+
         DatasetGraphAccessControl dsg1 = new DatasetGraphAccessControl(dsgBase, reg);
         return dsg1;
-    } 
+    }
 
     /**
      * Return whether a {@code DatasetGraph} has access control, either because it is wrapped in
@@ -125,12 +125,12 @@ public class DataAccessCtl {
         // Unfortunately that means find all named graphs.
 //        if ( sCxt instanceof SecurityContextAllowNamedGraphs ) {
 //        }
-        
+
         Collection<Node> names = sCxt.visibleGraphs();
         if ( names == null )
-            // XXX does not scale.
+            // TODO does not scale.
             names = Iter.toList(dsg.listGraphNodes());
-        
+
         return new DatasetGraphFilteredView(dsg, sCxt.predicateQuad(), sCxt.visibleGraphs());
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessLib.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessLib.java
index 0f04b81..e663ec9 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessLib.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessLib.java
@@ -29,8 +29,8 @@ import org.apache.jena.sparql.core.DatasetGraph;
 
 /** Package-only operations */
 class DataAccessLib {
-    
-    /** Determine the {@link SecurityContext} for this request */  
+
+    /** Determine the {@link SecurityContext} for this request */
     static SecurityContext getSecurityContext(HttpAction action, DatasetGraph dataset, Function<HttpAction, String> requestUser) {
         AuthorizationService registry = getAuthorizationService(action, dataset);
         if ( registry == null )
@@ -38,15 +38,15 @@ class DataAccessLib {
 
         SecurityContext sCxt = null;
         String user = requestUser.apply(action);
-        
+
         // User "*", users "_";
-        
+
         sCxt = registry.get(user);
         if ( sCxt == null )
             sCxt = noSecurityPolicy();
         return sCxt;
     }
-    
+
     /** Get the {@link AuthorizationService} for an action/query/dataset */
     static AuthorizationService getAuthorizationService(HttpAction action, DatasetGraph dsg) {
         if ( dsg instanceof DatasetGraphAccessControl )
@@ -59,7 +59,7 @@ class DataAccessLib {
         // Should not get here.
         throw new InternalError();
     }
-    
+
     static DatasetGraph decideDataset(HttpAction action, Function<HttpAction, String> requestUser) {
         DatasetGraph dsg = action.getDataset();
         if ( dsg == null )
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java
index 44a8147..6dee5ae 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java
@@ -23,27 +23,27 @@ import java.util.Objects;
 import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.DatasetGraphWrapper;
 
-/** DatasetGraph layer that carries an {@link AuthorizationService}. */ 
+/** DatasetGraph layer that carries an {@link AuthorizationService}. */
 public class DatasetGraphAccessControl extends DatasetGraphWrapper {
-    
-    private AuthorizationService registry = null; 
+
+    private AuthorizationService registry = null;
 
     /*package*/ DatasetGraphAccessControl(DatasetGraph dsg, AuthorizationService authService) {
         super(Objects.requireNonNull(dsg));
-        this.registry = Objects.requireNonNull(authService); 
+        this.registry = Objects.requireNonNull(authService);
    }
-    
+
     public AuthorizationService getAuthService() {
         return registry;
     }
 
-    // XXX Settings will be pushed down to the wrapped dataset.
+    // TODO Settings will be pushed down to the wrapped dataset.
     // A problem for DataAccessCtl.symAuthorizationService.
 //    @Override
 //    public Context getContext() {
 //        return super.getContext();
 //    }
-    
+
     /**
      * Return the underlying {@code DatasetGraph}. If the argument is not a
      * {@code DatasetGraphAccessControl}, return the argument.
@@ -53,7 +53,7 @@ public class DatasetGraphAccessControl extends DatasetGraphWrapper {
             return dsg;
         return ((DatasetGraphAccessControl)dsg).getWrapped();
     }
-    
+
     /**
      * Return the underlying {@code DatasetGraph}. If the argument is not a
      * {@code DatasetGraphAccessControl}, return null.
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilter.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilter.java
index 6059ed1..0e6248e 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilter.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilter.java
@@ -34,7 +34,7 @@ import org.apache.jena.sparql.util.Symbol;
  * {@link #test(Tuple)} if the tuple graph slot is in the collection of graph names or
  * matchDefaultGraph is true. It can be used as an "allow" filter; it can be negated to
  * become a "deny" filter.
- * 
+ *
  * @see GraphFilterTDB1#graphFilter
  * @see GraphFilterTDB2#graphFilter
  */
@@ -43,29 +43,29 @@ public abstract class GraphFilter<X> implements Predicate<Tuple<X>> {
     private final boolean matchDefaultGraph;
 //    // This makes the GraphFilter stateful.
 //    private X slot = null;
-    
+
     protected GraphFilter(Collection<X> matches, boolean matchDefaultGraph) {
         this.graphs = new HashSet<X>(matches);
         this.matchDefaultGraph = matchDefaultGraph;
     }
-    
+
     public static Symbol getContextKey(DatasetGraph dsg) {
         dsg = DatasetGraphAccessControl.removeWrapper(dsg);
-        
+
         if ( org.apache.jena.tdb.sys.TDBInternal.isTDB1(dsg) )
             return org.apache.jena.tdb.sys.SystemTDB.symTupleFilter;
         if ( org.apache.jena.tdb2.sys.TDBInternal.isTDB2(dsg) )
             return org.apache.jena.tdb2.sys.SystemTDB.symTupleFilter;
         throw new IllegalArgumentException("Not a TDB database");
     }
-    
+
     public abstract Symbol getContextKey();
-    
+
     @Override
     public boolean test(Tuple<X> t) {
         if ( t.len() == 3 ) {
             // Default graph.
-            return matchDefaultGraph; 
+            return matchDefaultGraph;
         }
         X g = t.get(0);
         boolean b = perGraphTest(g);
@@ -81,7 +81,7 @@ public abstract class GraphFilter<X> implements Predicate<Tuple<X>> {
 //        }
 //        boolean b = matches.contains(g);
 //        if ( b )
-//            slot = g ;
+//            slot = g;
 //        return b;
     }
 }
\ No newline at end of file
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB1.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB1.java
index 8fb94c5..70de136 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB1.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB1.java
@@ -32,7 +32,7 @@ import org.apache.jena.tdb.store.nodetable.NodeTable;
 import org.apache.jena.tdb.sys.SystemTDB;
 import org.apache.jena.tdb.sys.TDBInternal;
 
-/** {@link GraphFilter} for TDB1 */ 
+/** {@link GraphFilter} for TDB1 */
 class GraphFilterTDB1 extends GraphFilter<NodeId> {
 
     private GraphFilterTDB1(Collection<NodeId> matches, boolean matchDefaultGraph) {
@@ -43,7 +43,7 @@ class GraphFilterTDB1 extends GraphFilter<NodeId> {
     public Symbol getContextKey() {
         return SystemTDB.symTupleFilter;
     }
-    
+
     /**
      * Create a graph filter for a TDB1 {@link DatasetGraph}. The filter matches (returns
      * true) for Tuples where the graph slot in quad is in the collection or for triples in the default
@@ -52,10 +52,10 @@ class GraphFilterTDB1 extends GraphFilter<NodeId> {
     public static GraphFilterTDB1 graphFilter(DatasetGraph dsg, Collection<Node> namedGraphs, boolean matchDefaultGraph) {
         if ( ! TDBInternal.isTDB1(dsg) )
             throw new IllegalArgumentException("DatasetGraph is not TDB1-backed");
-        List<NodeId> x =  
+        List<NodeId> x =
             Txn.calculateRead(dsg, ()->{
                 NodeTable nt = TDBInternal.getDatasetGraphTDB(dsg).getQuadTable().getNodeTupleTable().getNodeTable();
-                return 
+                return
                     ListUtils.toList(
                         namedGraphs.stream()
                         .map(n->nt.getNodeIdForNode(n))
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB2.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB2.java
index a336ffd..bcfcb9f 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB2.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB2.java
@@ -43,7 +43,7 @@ class GraphFilterTDB2 extends GraphFilter<NodeId> {
     public Symbol getContextKey() {
         return SystemTDB.symTupleFilter;
     }
-    
+
     /**
      * Create a graph filter for a TDB2 {@link DatasetGraph}. The filter matches (returns
      * true) for Tuples where the graph slot in quad is in the collection or for triples in the default
@@ -52,10 +52,10 @@ class GraphFilterTDB2 extends GraphFilter<NodeId> {
     public static GraphFilterTDB2 graphFilter(DatasetGraph dsg, Collection<Node> namedGraphs, boolean matchDefaultGraph) {
         if ( ! TDBInternal.isTDB2(dsg) )
             throw new IllegalArgumentException("DatasetGraph is not TDB2-backed");
-        List<NodeId> x =  
+        List<NodeId> x =
             Txn.calculateRead(dsg, ()->{
                 NodeTable nt = TDBInternal.getDatasetGraphTDB(dsg).getQuadTable().getNodeTupleTable().getNodeTable();
-                return 
+                return
                     ListUtils.toList(
                         namedGraphs.stream()
                         .map(n->nt.getNodeIdForNode(n))
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/InitSecurity.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/InitSecurity.java
index 55e760e..ad81e2c 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/InitSecurity.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/InitSecurity.java
@@ -25,14 +25,14 @@ public class InitSecurity implements JenaSubsystemLifecycle {
 
     @Override
     public void start() {
-        JenaSystem.logLifecycle("InitSecurity - start") ;
+        JenaSystem.logLifecycle("InitSecurity - start");
         VocabSecurity.init();
-        JenaSystem.logLifecycle("InitSecurity - finish") ;
+        JenaSystem.logLifecycle("InitSecurity - finish");
     }
 
     @Override
     public void stop() {}
-    
+
     @Override
     public int level() { return 100; }
 }
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java
index d6ae465..45ccafc 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java
@@ -32,14 +32,14 @@ import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.Quad;
 import org.apache.jena.sparql.util.Context;
 
-/** A {@link SecurityContext} is the things actor (user, role) is allowed to do. 
+/** A {@link SecurityContext} is the things actor (user, role) is allowed to do.
  * Currently version: the set of graphs, by graph name, they can access.
  * It can be inverted into a "deny" policy with {@link Predicate#negate()}.
- */ 
+ */
 public interface SecurityContext {
     public static final SecurityContext NONE = new SecurityContextAllowNone();
     public static final SecurityContext ALL = new SecurityContextAllowAll();
-    public static SecurityContext ALL_NG(DatasetGraph dsg) { 
+    public static SecurityContext ALL_NG(DatasetGraph dsg) {
         Collection<Node> names = Iter.toList(dsg.listGraphNodes());
         //return new SecurityContextAllowNamedGraphs(dsg);
         return new SecurityContextView(names);
@@ -49,13 +49,13 @@ public interface SecurityContext {
     public static final Node allNamedGraphs = NodeFactory.createURI("urn:jena:accessAllNamedGraphs");
     public static final Node allNamedGraphsStr = NodeFactory.createLiteral("*");
     public static final Node allGraphsStr = NodeFactory.createLiteral("**");
-    
+
     /**
      * Collection of visible graph names. This method return null for null for "all" to avoid
      * needing to calculate the current set of named graph names.
      */
     public Collection<Node> visibleGraphs();
-    
+
     /**
      * Collection of visible graph URI names. This method return null for null for "all" to avoid
      * needing to calculate the current set of named graph names.
@@ -66,15 +66,15 @@ public interface SecurityContext {
         return visibleGraphs().stream()
                 .filter(Node::isURI)
                 .map(Node::getURI)
-                .collect(Collectors.toList()) ;
+                .collect(Collectors.toList());
     }
-    
+
     public boolean visableDefaultGraph();
 
     public default QueryExecution createQueryExecution(String queryString, DatasetGraph dsg) {
         return createQueryExecution(QueryFactory.create(queryString), dsg);
     }
-    
+
     public QueryExecution createQueryExecution(Query query, DatasetGraph dsg);
 
     /**
@@ -83,7 +83,7 @@ public interface SecurityContext {
      * efficient.
      */
     public Predicate<Quad> predicateQuad();
-    
+
     /**
      * Apply a filter suitable for the TDB-backed {@link DatasetGraph}, to the {@link Context} of the
      * {@link QueryExecution}. This does not modify the {@link DatasetGraph}.
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowAll.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowAll.java
index 9ab7053..125b984 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowAll.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowAll.java
@@ -28,19 +28,19 @@ import org.apache.jena.query.QueryExecutionFactory;
 import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.Quad;
 
-/** A {@link SecurityContext} that allow any graph, default or named. */ 
+/** A {@link SecurityContext} that allow any graph, default or named. */
 public class SecurityContextAllowAll implements SecurityContext {
-    
+
     public SecurityContextAllowAll() {}
-    
+
     @Override
     public Collection<Node> visibleGraphs() {
         // null means "all".
         return null;
     }
-    
+
     @Override
-    public boolean visableDefaultGraph() { return true; }  
+    public boolean visableDefaultGraph() { return true; }
 
     @Override
     public QueryExecution createQueryExecution(Query query, DatasetGraph dsg) {
@@ -54,7 +54,7 @@ public class SecurityContextAllowAll implements SecurityContext {
      */
     @Override
     public Predicate<Quad> predicateQuad() { return q->true; }
-    
+
     @Override
     public void filterTDB(DatasetGraph dsg, QueryExecution qExec) {
         // No filter necessary.
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNamedGraphs.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNamedGraphs.java
index bae61bb..da5c21b 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNamedGraphs.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNamedGraphs.java
@@ -28,23 +28,23 @@ import org.apache.jena.query.QueryExecutionFactory;
 import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.Quad;
 
-/** A {@link SecurityContext} that allow to named graph but not the default graph. */ 
+/** A {@link SecurityContext} that allow to named graph but not the default graph. */
 public class SecurityContextAllowNamedGraphs implements SecurityContext {
-    
+
     public SecurityContextAllowNamedGraphs() {}
-    
+
     @Override
     public Collection<Node> visibleGraphs() {
         // null means "all".
         return null;
     }
-    
+
     @Override
-    public boolean visableDefaultGraph() { return true; }  
+    public boolean visableDefaultGraph() { return true; }
 
     @Override
     public QueryExecution createQueryExecution(Query query, DatasetGraph dsg) {
-        
+
         return QueryExecutionFactory.create(query, dsg);
     }
 
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNone.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNone.java
index ed5defb..f24cc24 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNone.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNone.java
@@ -30,27 +30,27 @@ import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.DatasetGraphSink;
 import org.apache.jena.sparql.core.Quad;
 
-/** A {@link SecurityContext} that does not allow any access. */ 
+/** A {@link SecurityContext} that does not allow any access. */
 public class SecurityContextAllowNone implements SecurityContext {
-    
+
     public SecurityContextAllowNone() {}
-    
+
     @Override
     public Collection<Node> visibleGraphs() {
         return Collections.emptyList();
     }
-    
+
     @Override
-    public boolean visableDefaultGraph() { return false; }  
+    public boolean visableDefaultGraph() { return false; }
 
     @Override
     public QueryExecution createQueryExecution(Query query, DatasetGraph dsg) {
         return QueryExecutionFactory.create(query, new DatasetGraphSink());
     }
-    
+
     @Override
-    public Predicate<Quad> predicateQuad() { return q -> false ; }
-    
+    public Predicate<Quad> predicateQuad() { return q -> false; }
+
     @Override
     public void filterTDB(DatasetGraph dsg, QueryExecution qExec) {
         Predicate<?> pred = tuple->false;
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextView.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextView.java
index 8660b2c..869279f 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextView.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextView.java
@@ -36,18 +36,18 @@ import org.apache.jena.sparql.util.NodeUtils;
 import org.apache.jena.tdb.TDBFactory;
 import org.apache.jena.tdb2.DatabaseMgr;
 
-/** A {@link SecurityContextView} is the things actor (user, role) is allowed to do. 
+/** A {@link SecurityContextView} is the things actor (user, role) is allowed to do.
  * Currently version: the set of graphs, by graph name, they can access.
  * It can be inverted into a "deny" policy with {@link Predicate#negate()}.
- */ 
+ */
 public class SecurityContextView implements SecurityContext {
-    
+
     public static SecurityContextView NONE = new SecurityContextView();
     public static SecurityContextView DFT_GRAPH = new SecurityContextView(true);
 
     private final Collection<Node> graphNames;
     private final boolean matchDefaultGraph;
-    
+
     private SecurityContextView() {
         this(false);
     }
@@ -75,12 +75,12 @@ public class SecurityContextView implements SecurityContext {
         }
         this.graphNames = Collections.unmodifiableCollection(x);
     }
-    
+
     @Override
     public Collection<Node> visibleGraphs() {
         return graphNames;
     }
-    
+
     @Override
     public boolean visableDefaultGraph() {
         return matchDefaultGraph;
@@ -90,7 +90,7 @@ public class SecurityContextView implements SecurityContext {
     public QueryExecution createQueryExecution(String queryString, DatasetGraph dsg) {
         return createQueryExecution(QueryFactory.create(queryString), dsg);
     }
-    
+
     @Override
     public QueryExecution createQueryExecution(Query query, DatasetGraph dsg) {
         if ( isAccessControlledTDB(dsg) ) {
@@ -102,7 +102,7 @@ public class SecurityContextView implements SecurityContext {
         DatasetGraph dsgA = DataAccessCtl.filteredDataset(dsg, this);
         return QueryExecutionFactory.create(query, dsgA);
     }
-    
+
     /**
      * Apply a filter suitable for the TDB-backed {@link DatasetGraph}, to the {@link Context} of the
      * {@link QueryExecution}. This does not modify the {@link DatasetGraph}.
@@ -123,7 +123,7 @@ public class SecurityContextView implements SecurityContext {
         return quad -> {
             if ( quad.isDefaultGraph() )
                 return matchDefaultGraph;
-            if ( quad.isUnionGraph() ) 
+            if ( quad.isUnionGraph() )
                 // Union graph is automatically there but its visible contents are different.
                 return true;
             return graphNames.contains(quad.getGraph());
@@ -132,7 +132,7 @@ public class SecurityContextView implements SecurityContext {
 
     /**
      * Create a GraphFilter for a TDB backed dataset.
-     * 
+     *
      * @return GraphFilter
      * @throws IllegalArgumentException
      *             if not a TDB database, or a {@link DatasetGraphAccessControl} wrapped
@@ -141,7 +141,7 @@ public class SecurityContextView implements SecurityContext {
     protected GraphFilter<?> predicate(DatasetGraph dsg) {
         dsg = DatasetGraphAccessControl.removeWrapper(dsg);
         // dsg has to be the database dataset, not wrapped.
-        //  DatasetGraphSwitchable is wrapped but should not be unwrapped. 
+        //  DatasetGraphSwitchable is wrapped but should not be unwrapped.
         if ( TDBFactory.isTDB1(dsg) )
             return GraphFilterTDB1.graphFilter(dsg, graphNames, matchDefaultGraph);
         if ( DatabaseMgr.isTDB2(dsg) )
@@ -159,7 +159,7 @@ public class SecurityContextView implements SecurityContext {
             return true;
         return false;
     }
-    
+
     @Override
     public String toString() {
         return "dft:"+matchDefaultGraph+" / "+graphNames.toString();
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityRegistry.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityRegistry.java
index 1810d1a..7a1823c 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityRegistry.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityRegistry.java
@@ -26,11 +26,11 @@ import org.apache.jena.atlas.lib.Registry;
  * A registry mapping from a string (typically a user name or role
  * name) to a {@link SecurityContext}, where the {@link SecurityContext}
  * is the access control operations for that user/role.
- */ 
+ */
 public class SecurityRegistry extends Registry<String, SecurityContext> implements AuthorizationService {
-    
+
     public SecurityRegistry() {}
-    
+
     @Override
     public SecurityContext get(String actor) {
         if ( actor == null )
@@ -40,12 +40,12 @@ public class SecurityRegistry extends Registry<String, SecurityContext> implemen
             sCxt = SecurityContext.NONE;
         return sCxt;
     }
-    
-    @Override 
+
+    @Override
     public String toString() {
         return "SecurityRegistry"+keys();
-    }        
- 
+    }
+
     public String toLongString() {
         // Long form.
         StringJoiner sj1 = new StringJoiner("\n", "{ SecurityRegistry\n", "\n}");
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/VocabSecurity.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/VocabSecurity.java
index 14ed959..f6caf0b 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/VocabSecurity.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/VocabSecurity.java
@@ -25,8 +25,8 @@ import org.apache.jena.tdb.assembler.Vocab;
 
 public class VocabSecurity {
     private static final String NS = "http://jena.apache.org/access#";
-    
-    public static String getURI() { return NS ; } 
+
+    public static String getURI() { return NS; }
 
     // Types
     public static final Resource tAccessControlledDataset = Vocab.type(NS, "AccessControlledDataset");
@@ -40,10 +40,10 @@ public class VocabSecurity {
     public static final Property pUser                    = Vocab.property(NS, "user");
     public static final Property pGraphs                  = Vocab.property(NS, "graphs");
 
-    private static boolean initialized = false ; 
-    
-    static { init() ; }
-    
+    private static boolean initialized = false;
+
+    static { init(); }
+
     static synchronized public void init() {
         if ( initialized )
             return;
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_Access.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_Access.java
index 103f75e..ee84e6e 100644
--- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_Access.java
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_Access.java
@@ -28,6 +28,6 @@ import org.junit.runners.Suite;
     TestSecurityFilterLocal.class
     , TestSecurityRegistry.class
 })
-    
+
 public class TS_Access {
 }
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterLocal.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterLocal.java
index 0f9bd11..91c334f 100644
--- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterLocal.java
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterLocal.java
@@ -71,13 +71,13 @@ public class TestSecurityFilterLocal {
 
         Object[] obj1 = { "TDB/db", c1, true};
         Object[] obj2 = { "TDB2/db", c2, true };
-        
+
         // By adding the general, but slower, DatasetGraphFilter
         Object[] obj3 = { "TDB/filtered", c1, false };
         Object[] obj4 = { "TDB2/filtered", c2, false };
         Object[] obj5 = { "TIM/filtered", c3, false };
         Object[] obj6 = { "Plain/filtered", c4, false };
-        
+
         List<Object[]> x = new ArrayList<>();
         return Arrays.asList(obj1, obj2, obj3, obj4, obj5, obj6);
 //        x.add(obj1);
@@ -85,12 +85,12 @@ public class TestSecurityFilterLocal {
 //        x.add(obj5);
 //        return x;
     }
-    
+
     private final DatasetGraph testdsg;
     private SecurityRegistry reg = new SecurityRegistry();
     private final boolean applyFilterDSG;
     private final boolean applyFilterTDB;
-    
+
     public TestSecurityFilterLocal(String name, Creator<DatasetGraph> source, boolean applyFilterTDB) {
         DatasetGraph dsgBase = source.create();
         addTestData(dsgBase);
@@ -99,17 +99,17 @@ public class TestSecurityFilterLocal {
         reg.put("user0", new SecurityContextView(Quad.defaultGraphIRI.getURI()));
         reg.put("user1", new SecurityContextView("http://test/g1", Quad.defaultGraphIRI.getURI()));
         reg.put("user2", new SecurityContextView("http://test/g1", "http://test/g2", "http://test/g3"));
-        
-        // and graphs "**", "*" 
+
+        // and graphs "**", "*"
         reg.put("*", new SecurityContextView("http://test/g1"));
         reg.put("_", new SecurityContextView("http://test/g1"));
-        
-        
+
+
         testdsg = DataAccessCtl.controlledDataset(dsgBase, reg);
         this.applyFilterTDB = applyFilterTDB;
         this.applyFilterDSG = ! applyFilterTDB;
     }
-    
+
     private static String queryAll        = "SELECT * { { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } } }";
     private static String queryDft        = "SELECT * { ?s ?p ?o }";
     private static String queryNamed      = "SELECT * { GRAPH ?g { ?s ?p ?o } }";
@@ -139,7 +139,7 @@ public class TestSecurityFilterLocal {
                 }
             });
     }
-    
+
     private Set<Node> subjects(DatasetGraph dsg,  Function<DatasetGraph, Graph> graphChoice, String queryString, SecurityContext sCxt) {
         final DatasetGraph dsg1 = applyFilterDSG
             ? DataAccessCtl.filteredDataset(dsg, sCxt)
@@ -225,37 +225,37 @@ public class TestSecurityFilterLocal {
 
     @Test public void graph_names_userNone() {
         SecurityContext sCxt = reg.get("userNone");
-        Set<Node> visible = graphs(testdsg, sCxt); 
+        Set<Node> visible = graphs(testdsg, sCxt);
         assertSeen(visible);
     }
-    
+
     @Test public void graph_names_userDft() {
         SecurityContext sCxt = reg.get("userDft");
-        Set<Node> visible = graphs(testdsg, sCxt); 
+        Set<Node> visible = graphs(testdsg, sCxt);
         assertSeen(visible);
     }
-    
+
     @Test public void graph_names_user0() {
         SecurityContext sCxt = reg.get("user0");
-        Set<Node> visible = graphs(testdsg, sCxt); 
+        Set<Node> visible = graphs(testdsg, sCxt);
         assertSeen(visible);
     }
-    
+
     @Test public void graph_names_user1() {
         SecurityContext sCxt = reg.get("user1");
-        Set<Node> visible = graphs(testdsg, sCxt); 
+        Set<Node> visible = graphs(testdsg, sCxt);
         assertSeen(visible, g1);
     }
 
     @Test public void graph_names_user2() {
         SecurityContext sCxt = reg.get("user2");
-        Set<Node> visible = graphs(testdsg, sCxt); 
+        Set<Node> visible = graphs(testdsg, sCxt);
         assertSeen(visible, g1, g2, g3);
     }
 
     @Test public void graph_names_userX() {
         SecurityContext sCxt = reg.get("userX");
-        Set<Node> visible = graphs(testdsg, sCxt); 
+        Set<Node> visible = graphs(testdsg, sCxt);
         assertSeen(visible);
     }
 
@@ -279,11 +279,11 @@ public class TestSecurityFilterLocal {
         }
         assertSeen(visible, expected);
     }
-    
+
     @Test public void filter_union_userNone() {
         filter_union_user("userNone");
     }
-    
+
     @Test public void filter_union_userDft() {
         // Storage default graph not visible with a union query.
         filter_union_user("userDft");
@@ -293,25 +293,25 @@ public class TestSecurityFilterLocal {
         // Storage default graph not visible with a union query.
         filter_union_user("user0");
     }
-    
+
     @Test public void filter_union_user1() {
         filter_union_user("user1", s1);
     }
-    
+
     @Test public void filter_union_user2() {
         filter_union_user("user2", s1, s2, s3);
     }
-    
+
     @Test public void filter_union_userX() {
         filter_union_user("userX");
     }
-    
-    
+
+
     // Graph/Model
     @Test public void query_model_userNone() {
         query_model_user(testdsg, dsg->dsg.getDefaultGraph(), "userNone");
     }
-    
+
     @Test public void query_model_userDft() {
         query_model_user(testdsg, dsg->dsg.getDefaultGraph(), "userDft", s0);
     }
@@ -347,7 +347,7 @@ public class TestSecurityFilterLocal {
     @Test public void query_model_ng_user22() {
         query_model_user(testdsg, dsg->dsg.getGraph(g2), "user2", s2);
     }
-    
+
     @Test public void query_model_userXa() {
         query_model_user(testdsg, dsg->dsg.getDefaultGraph(), "userX");
     }
@@ -361,8 +361,8 @@ public class TestSecurityFilterLocal {
         Set<Node> visible = subjects(dsg, graphChoice, queryDft, sCxt);
         assertSeen(visible, expected);
     }
-    
-    private static String dataStr = StrUtils.strjoinNL 
+
+    private static String dataStr = StrUtils.strjoinNL
         ("PREFIX : <http://test/>"
             ,""
             ,":s0 :p 0 ."
@@ -373,23 +373,23 @@ public class TestSecurityFilterLocal {
             );
 
 
-    public static Node s0 = SSE.parseNode("<http://test/s0>"); 
-    public static Node s1 = SSE.parseNode("<http://test/s1>"); 
-    public static Node s2 = SSE.parseNode("<http://test/s2>"); 
-    public static Node s3 = SSE.parseNode("<http://test/s3>"); 
-    public static Node s4 = SSE.parseNode("<http://test/s4>"); 
+    public static Node s0 = SSE.parseNode("<http://test/s0>");
+    public static Node s1 = SSE.parseNode("<http://test/s1>");
+    public static Node s2 = SSE.parseNode("<http://test/s2>");
+    public static Node s3 = SSE.parseNode("<http://test/s3>");
+    public static Node s4 = SSE.parseNode("<http://test/s4>");
 
-    public static Node g1 = SSE.parseNode("<http://test/g1>"); 
-    public static Node g2 = SSE.parseNode("<http://test/g2>"); 
-    public static Node g3 = SSE.parseNode("<http://test/g3>"); 
-    public static Node g4 = SSE.parseNode("<http://test/g4>"); 
+    public static Node g1 = SSE.parseNode("<http://test/g1>");
+    public static Node g2 = SSE.parseNode("<http://test/g2>");
+    public static Node g3 = SSE.parseNode("<http://test/g3>");
+    public static Node g4 = SSE.parseNode("<http://test/g4>");
 
     public static void addTestData(DatasetGraph dsg) {
         Txn.executeWrite(dsg, ()->{
             RDFParser.create().fromString(dataStr).lang(Lang.TRIG).parse(dsg);
         });
     }
-    
+
     public static void assertSeen(Set<Node> visible, Node ... expected) {
         Set<Node> expectedNodes = new HashSet<>(Arrays.asList(expected));
         assertEquals(expectedNodes, visible);
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityRegistry.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityRegistry.java
index 0e62bd7..f3bf5d9 100644
--- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityRegistry.java
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityRegistry.java
@@ -26,7 +26,7 @@ import org.apache.jena.sparql.core.assembler.AssemblerUtils;
 import org.apache.jena.sys.JenaSystem;
 import org.junit.Test;
 
-/** Test parsing of assemblers with security aspects */ 
+/** Test parsing of assemblers with security aspects */
 public class TestSecurityRegistry {
     static { JenaSystem.init(); }
     static final String DIR = "testing/SecurityRegistry/";
@@ -38,10 +38,10 @@ public class TestSecurityRegistry {
         assertEquals(4, sReg.keys().size());
         assertEquals(3, sReg.get("user1").visibleGraphs().size());
     }
-    
+
     @Test public void assemblerFile_2() {
         // WIP
-        //   user1, all named graphs                     
+        //   user1, all named graphs
         //   user2, all graphs
         //   user3, all named graphs +dft == all graphs
         //   any user, graph1
@@ -54,26 +54,26 @@ public class TestSecurityRegistry {
             Node x = sCxt.visibleGraphs().stream().findFirst().get();
             assertEquals(SecurityContext.allNamedGraphs, x);
         }
-        
+
         {
             SecurityContext sCxt = authService.get("user2");
             assertEquals(1, sCxt.visibleGraphs().size());
             Node x = sCxt.visibleGraphs().stream().findFirst().get();
             assertEquals(SecurityContext.allGraphs, x);
         }
-        
+
         {
             SecurityContext sCxt = authService.get("user3");
             assertEquals(1, sCxt.visibleGraphs().size());
             Node x = sCxt.visibleGraphs().stream().findFirst().get();
             assertEquals(SecurityContext.allGraphs, x);
         }
-        
+
         {
             SecurityContext sCxt = authService.get("*");
             assertEquals(1, sCxt.visibleGraphs().size());
             String x = sCxt.visibleGraphNames().stream().findFirst().get();
             assertEquals("http://host/graphname1", x);
-        }            
+        }
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/pom.xml b/jena-fuseki2/jena-fuseki-core/pom.xml
index 8cc4e29..d233ebb 100644
--- a/jena-fuseki2/jena-fuseki-core/pom.xml
+++ b/jena-fuseki2/jena-fuseki-core/pom.xml
@@ -135,6 +135,12 @@
               <goal>jar-no-fork</goal>
             </goals>
           </execution>
+          <execution>
+            <id>attach-sources-test</id>
+            <goals>
+              <goal>test-jar-no-fork</goal>
+            </goals>
+          </execution>
         </executions>
       </plugin>
 
@@ -156,7 +162,14 @@
               <Automatic-Module-Name>${automatic.module.name}</Automatic-Module-Name>
             </manifestEntries>
           </archive>
-        </configuration>
+        </configuration> 
+        <executions>
+          <execution>
+            <goals>
+              <goal>test-jar</goal>
+            </goals>
+          </execution>
+        </executions>
       </plugin>
       
       <plugin>
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/DEF.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/DEF.java
index 05e9bf9..67442fd 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/DEF.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/DEF.java
@@ -18,21 +18,21 @@
 
 package org.apache.jena.fuseki;
 
-import org.apache.jena.atlas.web.AcceptList ;
-import org.apache.jena.atlas.web.MediaType ;
-import static org.apache.jena.riot.WebContent.* ;
+import org.apache.jena.atlas.web.AcceptList;
+import org.apache.jena.atlas.web.MediaType;
+import static org.apache.jena.riot.WebContent.*;
 
 public class DEF
 {
-    public static final MediaType acceptRDFXML        = MediaType.create(contentTypeRDFXML) ;
-    public static final MediaType acceptNQuads        = MediaType.create(contentTypeNQuads) ;
-    public static final MediaType acceptRSXML         = MediaType.create(contentTypeResultsXML) ;
-    public static final MediaType acceptJSON          = MediaType.create(contentTypeJSON) ;
-    public static final MediaType acceptTurtle        = MediaType.create(contentTypeTurtle) ;
-    
-    public static final AcceptList jsonOffer          = AcceptList.create(contentTypeJSON) ;
+    public static final MediaType acceptRDFXML        = MediaType.create(contentTypeRDFXML);
+    public static final MediaType acceptNQuads        = MediaType.create(contentTypeNQuads);
+    public static final MediaType acceptRSXML         = MediaType.create(contentTypeResultsXML);
+    public static final MediaType acceptJSON          = MediaType.create(contentTypeJSON);
+    public static final MediaType acceptTurtle        = MediaType.create(contentTypeTurtle);
 
-    public static final AcceptList constructOffer     = AcceptList.create(contentTypeTurtle, 
+    public static final AcceptList jsonOffer          = AcceptList.create(contentTypeJSON);
+
+    public static final AcceptList constructOffer     = AcceptList.create(contentTypeTurtle,
                                                                           contentTypeTurtleAlt1,
                                                                           contentTypeTurtleAlt2,
                                                                           contentTypeNTriples,
@@ -43,16 +43,16 @@ public class DEF
                                                                           contentTypeJSONLD,
                                                                           contentTypeRDFJSON,
                                                                           contentTypeRDFThrift,
-                                                                          
+
                                                                           contentTypeTriG,
                                                                           contentTypeTriGAlt1,
                                                                           contentTypeTriGAlt2,
                                                                           contentTypeNQuads,
                                                                           contentTypeNQuadsAlt1,
                                                                           contentTypeNQuadsAlt2
-                                                                          ) ;
-    
-    public static final AcceptList rdfOffer           = AcceptList.create(contentTypeTurtle, 
+                                                                          );
+
+    public static final AcceptList rdfOffer           = AcceptList.create(contentTypeTurtle,
                                                                           contentTypeTurtleAlt1,
                                                                           contentTypeTurtleAlt2,
                                                                           contentTypeNTriples,
@@ -63,19 +63,19 @@ public class DEF
                                                                           contentTypeJSONLD,
                                                                           contentTypeRDFJSON,
                                                                           contentTypeRDFThrift
-                                                                          ) ;
-    
+                                                                          );
+
     public static final AcceptList quadsOffer         = AcceptList.create(contentTypeTriG,
                                                                           contentTypeTriGAlt1,
                                                                           contentTypeTriGAlt2,
                                                                           contentTypeJSONLD,
                                                                           contentTypeNQuads,
                                                                           contentTypeNQuadsAlt1,
-                                                                          contentTypeNQuadsAlt2, 
+                                                                          contentTypeNQuadsAlt2,
                                                                           contentTypeTriX,
                                                                           contentTypeTriXxml
-                                                                          ) ;
-    
+                                                                          );
+
     // Offer for SELECT
     // This include application/xml and application/json.
     public static final AcceptList rsOfferTable       = AcceptList.create(contentTypeResultsJSON,
@@ -86,10 +86,10 @@ public class DEF
                                                                           contentTypeXML,
                                                                           contentTypeResultsThrift,
                                                                           contentTypeTextPlain
-                                                                          ) ;
-         
+                                                                          );
+
     // Offer for ASK
-    // This includes application/xml and application/json and excludes application/sparql-results+thrift 
+    // This includes application/xml and application/json and excludes application/sparql-results+thrift
     public static final AcceptList rsOfferBoolean      = AcceptList.create(contentTypeResultsJSON,
                                                                            contentTypeJSON,
                                                                            contentTypeTextCSV,
@@ -97,14 +97,17 @@ public class DEF
                                                                            contentTypeResultsXML,
                                                                            contentTypeXML,
                                                                            contentTypeTextPlain
-                                                                           ) ;
+                                                                           );
+
 
-    
     // Names for services in the default configuration
-    public static final String ServiceQuery         = "query" ;
-    public static final String ServiceQueryAlt      = "sparql" ;
-    public static final String ServiceUpdate        = "update" ;
-    public static final String ServiceData          = "data" ;
-    public static final String ServiceUpload        = "upload" ;
-    public static final String ServiceGeneralQuery  = "/sparql" ;
+    public static final String ServiceQuery         = "query";
+    public static final String ServiceQueryAlt      = "sparql";
+    public static final String ServiceUpdate        = "update";
+
+    public static final String ServiceDataset       = "dataset";
+    public static final String ServiceDatasetAlt    = "quads";
+    public static final String ServiceData          = "data";
+    public static final String ServiceUpload        = "upload";
+    public static final String ServiceGeneralQuery  = "/sparql";
 }
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 3d781c8..9105958 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
@@ -16,12 +16,12 @@
  * limitations under the License.
  */
 
-package org.apache.jena.fuseki ;
+package org.apache.jena.fuseki;
 
 import java.io.IOException;
-import java.util.Calendar ;
-import java.util.TimeZone ;
-import java.util.concurrent.TimeUnit ;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
 
 import javax.servlet.ServletContext;
 
@@ -31,79 +31,82 @@ import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpOptions;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.protocol.HttpContext;
-import org.apache.jena.atlas.lib.DateTimeUtils ;
+import org.apache.jena.atlas.lib.DateTimeUtils;
 import org.apache.jena.atlas.logging.Log;
 import org.apache.jena.atlas.web.HttpException;
 import org.apache.jena.fuseki.system.FusekiNetLib;
-import org.apache.jena.query.ARQ ;
+import org.apache.jena.query.ARQ;
 import org.apache.jena.rdfconnection.RDFConnectionRemote;
-import org.apache.jena.riot.system.stream.LocatorFTP ;
-import org.apache.jena.riot.system.stream.LocatorHTTP ;
-import org.apache.jena.riot.system.stream.StreamManager ;
+import org.apache.jena.riot.system.stream.LocatorFTP;
+import org.apache.jena.riot.system.stream.LocatorHTTP;
+import org.apache.jena.riot.system.stream.StreamManager;
 import org.apache.jena.riot.web.HttpOp;
-import org.apache.jena.sparql.SystemARQ ;
-import org.apache.jena.sparql.mgt.SystemInfo ;
-import org.apache.jena.sparql.util.Context ;
-import org.apache.jena.sparql.util.MappingRegistry ;
-import org.apache.jena.sys.JenaSystem ;
-import org.apache.jena.tdb.TDB ;
-import org.apache.jena.tdb.transaction.TransactionManager ;
+import org.apache.jena.sparql.SystemARQ;
+import org.apache.jena.sparql.mgt.SystemInfo;
+import org.apache.jena.sparql.util.Context;
+import org.apache.jena.sparql.util.MappingRegistry;
+import org.apache.jena.sys.JenaSystem;
+import org.apache.jena.tdb.TDB;
+import org.apache.jena.tdb.transaction.TransactionManager;
 import org.apache.jena.util.Metadata;
-import org.slf4j.Logger ;
-import org.slf4j.LoggerFactory ;
+import org.slf4j.Logger;
+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 String    PATH                         = "org.apache.jena.fuseki";
 
     /** a unique IRI for the Fuseki namespace */
-    static public String    FusekiIRI                    = "http://jena.apache.org/Fuseki" ;
+    static public 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 String    FusekiSymbolIRI              = "http://jena.apache.org/fuseki#";
 
     /** Default location of the pages for the Fuseki UI  */
-    static public String    PagesStatic                  = "pages" ;
+    static public 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/" ;
+    static public final String BaseUpload                = "http://server/unset-base/";
 
+    /** Add CORS header */
+    static public final boolean CORS_ENABLED = false;
+    
     /**
      * A relative resources path to the location of
      * <code>fuseki-properties.xml</code> file.
      */
-    static private String   metadataLocation             = "org/apache/jena/fuseki/fuseki-properties.xml" ;
+    static private String   metadataLocation             = "org/apache/jena/fuseki/fuseki-properties.xml";
 
     /**
      * Object which holds metadata specified within
      * {@link Fuseki#metadataLocation}
      */
-    static private Metadata metadata                     = initMetadata() ;
+    static private Metadata metadata                     = initMetadata();
 
     private static Metadata initMetadata() {
-        Metadata m = new Metadata() ;
-        // m.addMetadata(metadataDevLocation) ;
-        m.addMetadata(metadataLocation) ;
-        return m ;
+        Metadata m = new Metadata();
+        // m.addMetadata(metadataDevLocation);
+        m.addMetadata(metadataLocation);
+        return m;
     }
 
-    /** The name of the Fuseki server.*/ 
-    static public final String        NAME              = "Apache Jena Fuseki" ;
+    /** The name of the Fuseki server.*/
+    static public final String        NAME              = "Apache Jena Fuseki";
 
     /** Version of this Fuseki instance */
-    static public final String        VERSION           = metadata.get(PATH + ".version", "development") ;
+    static public final String        VERSION           = metadata.get(PATH + ".version", "development");
 
     /** Date when Fuseki was built */
-    static public final String        BUILD_DATE        = metadata.get(PATH + ".build.datetime", "unknown") ;
+    static public final String        BUILD_DATE        = metadata.get(PATH + ".build.datetime", "unknown");
 
     /** Supporting Graph Store Protocol direct naming.
      * <p>
@@ -123,84 +126,84 @@ public class Fuseki {
      *  </ul>
      *  <p>
      * <b>Note</b><br/>
-     * GSP Direct Naming was implemented to provide two implementations for the SPARQL 1.1 implementation report.  
+     * GSP Direct Naming was implemented to provide two implementations for the SPARQL 1.1 implementation report.
      */
-    static public final boolean       GSP_DIRECT_NAMING = false ;
+    static public final boolean       GSP_DIRECT_NAMING = false;
 
     /** Are we in development mode?  That means a SNAPSHOT, or no VERSION
      * because maven has not filtered the fuseki-properties.xml file.
      */
-    public static boolean   developmentMode ;
+    public static boolean   developmentMode;
     static {
         // See ServletBase.setCommonheaders
         // If it look like a SNAPSHOT, or it's not set, we are in development mode.
-        developmentMode = ( VERSION == null || VERSION.equals("development") || VERSION.contains("SNAPSHOT") ) ;
+        developmentMode = ( VERSION == null || VERSION.equals("development") || VERSION.contains("SNAPSHOT") );
     }
 
-    public static boolean   outputJettyServerHeader     = developmentMode ;
-    public static boolean   outputFusekiServerHeader    = developmentMode ;
-    
+    public static boolean   outputJettyServerHeader     = developmentMode;
+    public static boolean   outputFusekiServerHeader    = developmentMode;
+
     /** An identifier for the HTTP Fuseki server instance */
-    static public final String  serverHttpName          = NAME + " (" + VERSION + ")" ;
-    
+    static public final String  serverHttpName          = NAME + " (" + VERSION + ")";
+
     /** Logger name for operations */
-    public static final String        actionLogName     = PATH + ".Fuseki" ;
+    public static final String        actionLogName     = PATH + ".Fuseki";
 
     /** Instance of log for operations */
-    public static final Logger        actionLog         = LoggerFactory.getLogger(actionLogName) ;
+    public static final Logger        actionLog         = LoggerFactory.getLogger(actionLogName);
 
     /** Logger name for standard webserver log file request log */
-    public static final String        requestLogName    = PATH + ".Request" ;
+    public static final String        requestLogName    = PATH + ".Request";
 
     // See HttpAction.finishRequest.
     // Normally OFF
     /** Instance of a log for requests: format is NCSA. */
-    public static final Logger        requestLog        = LoggerFactory.getLogger(requestLogName) ;
+    public static final Logger        requestLog        = LoggerFactory.getLogger(requestLogName);
 
     /** Admin log file for operations. */
-    public static final String        adminLogName      = PATH + ".Admin" ;
+    public static final String        adminLogName      = PATH + ".Admin";
 
     /** Instance of log for operations. */
-    public static final Logger        adminLog          = LoggerFactory.getLogger(adminLogName) ;
+    public static final Logger        adminLog          = LoggerFactory.getLogger(adminLogName);
 
     /** Admin log file for operations. */
-    public static final String        builderLogName    = PATH + ".Builder" ;
+    public static final String        builderLogName    = PATH + ".Builder";
 
     /** Instance of log for operations. */
-    public static final Logger        builderLog        = LoggerFactory.getLogger(builderLogName) ;
+    public static final Logger        builderLog        = LoggerFactory.getLogger(builderLogName);
 
     /** Validation log file for operations. */
-    public static final String        validationLogName = PATH + ".Validate" ;
+    public static final String        validationLogName = PATH + ".Validate";
 
     /** Instance of log for validation. */
-    public static final Logger        validationLog     = LoggerFactory.getLogger(adminLogName) ;
+    public static final Logger        validationLog     = LoggerFactory.getLogger(adminLogName);
 
     /** Actual log file for general server messages. */
-    public static final String        serverLogName     = PATH + ".Server" ;
+    public static final String        serverLogName     = PATH + ".Server";
 
     /** Instance of log for general server messages. */
-    public static final Logger        serverLog         = LoggerFactory.getLogger(serverLogName) ;
+    public static final Logger        serverLog         = LoggerFactory.getLogger(serverLogName);
 
     /** Logger used for the servletContent.log operations (if settable -- depends on environment) */
-    public static final String        servletRequestLogName     = PATH + ".Servlet" ;
+    public static final String        servletRequestLogName     = PATH + ".Servlet";
 
     /** Actual log file for config server messages. */
-    public static final String        configLogName     = PATH + ".Config" ;
+    public static final String        configLogName     = PATH + ".Config";
 
     /** Instance of log for config server messages. */
-    public static final Logger        configLog         = LoggerFactory.getLogger(configLogName) ;
+    public static final Logger        configLog         = LoggerFactory.getLogger(configLogName);
 
     /** Instance of log for config server messages.
      * This is the global default used to set attribute
      * in each server created.
      */
-    public static boolean             verboseLogging    = false ;
+    public static boolean             verboseLogging    = false;
 
     // Servlet context attribute names,
 
     public static final String attrVerbose                 = "org.apache.jena.fuseki:verbose";
     public static final String attrNameRegistry            = "org.apache.jena.fuseki:DataAccessPointRegistry";
-    public static final String attrServiceRegistry         = "org.apache.jena.fuseki:ServiceDispatchRegistry";
+    public static final String attrOperationRegistry       = "org.apache.jena.fuseki:OperationRegistry";
     public static final String attrAuthorizationService    = "org.apache.jena.fuseki:AuthorizationService";
 
     public static void setVerbose(ServletContext cxt, boolean verbose) {
@@ -216,65 +219,65 @@ public class Fuseki {
      * through a location mapper whereby a name (e.g. URL) is redirected to
      * another name (e.g. local file).
      * */
-    public static final StreamManager webStreamManager ;
+    public static final StreamManager webStreamManager;
     static {
-        webStreamManager = new StreamManager() ;
+        webStreamManager = new StreamManager();
         // Only know how to handle http URLs
-        webStreamManager.addLocator(new LocatorHTTP()) ;
-        webStreamManager.addLocator(new LocatorFTP()) ;
+        webStreamManager.addLocator(new LocatorHTTP());
+        webStreamManager.addLocator(new LocatorFTP());
     }
 
     /** Default (and development) root of the Fuseki installation for fixed files. */
-    public static String DFT_FUSEKI_HOME = "." ;
+    public static String DFT_FUSEKI_HOME = ".";
     /** Default (and development) root of the varying files in this deployment. */
-    public static String DFT_FUSEKI_BASE = "." ;
+    public static String DFT_FUSEKI_BASE = ".";
 
-    private static boolean            initialized       = false ;
+    private static boolean            initialized       = false;
 
     // Server start time and uptime.
-    private static final long startMillis = System.currentTimeMillis() ;
+    private static final long startMillis = System.currentTimeMillis();
     // Hide server locale
-    private static final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("00:00")) ;
-    static { cal.setTimeInMillis(startMillis) ; }  // Exactly the same start point!
+    private static final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("00:00"));
+    static { cal.setTimeInMillis(startMillis); }  // Exactly the same start point!
 
-    private static final String startDateTime = DateTimeUtils.calendarToXSDDateTimeString(cal) ;
+    private static final String startDateTime = DateTimeUtils.calendarToXSDDateTimeString(cal);
 
     /** Return the number of milliseconds since the server started */
     public static long serverUptimeMillis() {
-        return System.currentTimeMillis() - startMillis ;
+        return System.currentTimeMillis() - startMillis;
     }
 
     /** Server uptime in seconds */
     public static long serverUptimeSeconds() {
-        long x = System.currentTimeMillis() - startMillis ;
-        return TimeUnit.MILLISECONDS.toSeconds(x) ;
+        long x = System.currentTimeMillis() - startMillis;
+        return TimeUnit.MILLISECONDS.toSeconds(x);
     }
 
     /** XSD DateTime for when the server started */
     public static String serverStartedAt() {
-        return startDateTime ;
+        return startDateTime;
     }
 
     /**
      * Initialize an instance of the Fuseki server stack.
      * This is not done via Jena's initialization mechanism
      * but done explicitly to give more control.
-     * Touching this class causes this to happen 
-     * (see static block at the end of this class). 
+     * Touching this class causes this to happen
+     * (see static block at the end of this class).
      */
     public synchronized static void init() {
         if ( initialized )
-            return ;
-        initialized = true ;
-        JenaSystem.init() ;
-        SystemInfo sysInfo = new SystemInfo(FusekiIRI, PATH, VERSION, BUILD_DATE) ;
-        SystemARQ.registerSubSystem(sysInfo) ;
-        MappingRegistry.addPrefixMapping("fuseki", FusekiSymbolIRI) ;
-
-        TDB.setOptimizerWarningFlag(false) ;
+            return;
+        initialized = true;
+        JenaSystem.init();
+        SystemInfo sysInfo = new SystemInfo(FusekiIRI, PATH, VERSION, BUILD_DATE);
+        SystemARQ.registerSubSystem(sysInfo);
+        MappingRegistry.addPrefixMapping("fuseki", FusekiSymbolIRI);
+
+        TDB.setOptimizerWarningFlag(false);
         // Don't set TDB batch commits.
         // This can be slower, but it less memory hungry and more predictable.
-        TransactionManager.QueueBatchSize = 0 ;
+        TransactionManager.QueueBatchSize = 0;
     }
 
     /**
@@ -283,19 +286,19 @@ public class Fuseki {
      * @return {@link org.apache.jena.query.ARQ#getContext()}
      */
     public static Context getContext() {
-        return ARQ.getContext() ;
+        return ARQ.getContext();
     }
 
     // Force a call to init.
     static {
-        init() ;
+        init();
     }
 
     /** Retrun a free port */
     public static int choosePort() {
         return FusekiNetLib.choosePort();
     }
-    
+
     /**
      * Test whether a URL identifies a Fuseki server. This operation can not guarantee to
      * detect a Fuseki server - for example, it may be behind a reverse proxy that masks
@@ -304,7 +307,7 @@ public class Fuseki {
     public static boolean isFuseki(String datasetURL) {
         HttpOptions request = new HttpOptions(datasetURL);
         HttpClient httpClient = HttpOp.getDefaultHttpClient();
-        if ( httpClient == null ) 
+        if ( httpClient == null )
             httpClient = HttpClients.createSystem();
         return isFuseki(request, httpClient, null);
     }
@@ -317,7 +320,7 @@ public class Fuseki {
     public static boolean isFuseki(RDFConnectionRemote connection) {
         HttpOptions request = new HttpOptions(connection.getDestination());
         HttpClient httpClient = connection.getHttpClient();
-        if ( httpClient == null ) 
+        if ( httpClient == null )
             httpClient = HttpClients.createSystem();
         HttpContext httpContext = connection.getHttpContext();
         return isFuseki(request, httpClient, httpContext);
@@ -328,11 +331,11 @@ public class Fuseki {
             HttpResponse response = httpClient.execute(request);
             // Fuseki does not send "Server" in release mode.
             // (best practice).
-            // All we can do is try for the "Fuseki-Request-ID" 
+            // All we can do is try for the "Fuseki-Request-ID"
             String reqId = safeGetHeader(response, "Fuseki-Request-ID");
             if ( reqId != null )
                 return true;
-    
+
             // If returning "Server"
             String serverIdent = safeGetHeader(response, "Server");
             if ( serverIdent != null ) {
@@ -342,7 +345,7 @@ public class Fuseki {
                     isFuseki = serverIdent.toLowerCase().contains("fuseki");
                 return isFuseki;
             }
-            return false; 
+            return false;
         } catch (IOException ex) {
             throw new HttpException("Failed to check for a Fuseki server", ex);
         }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/FusekiConfigException.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/FusekiConfigException.java
index 69d1e87..37d81db 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/FusekiConfigException.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/FusekiConfigException.java
@@ -20,8 +20,8 @@ package org.apache.jena.fuseki;
 
 public class FusekiConfigException extends FusekiException
 {
-    public FusekiConfigException(String msg, Throwable cause)    { super(msg, cause) ; }
-    public FusekiConfigException(String msg)                     { super(msg) ; }
-    public FusekiConfigException(Throwable cause)                { super(cause) ; }
-    public FusekiConfigException()                               { super() ; }
+    public FusekiConfigException(String msg, Throwable cause)    { super(msg, cause); }
+    public FusekiConfigException(String msg)                     { super(msg); }
+    public FusekiConfigException(Throwable cause)                { super(cause); }
+    public FusekiConfigException()                               { super(); }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/FusekiException.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/FusekiException.java
index fcf5361..50a5f70 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/FusekiException.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/FusekiException.java
@@ -18,12 +18,12 @@
 
 package org.apache.jena.fuseki;
 
-import org.apache.jena.sparql.ARQException ;
+import org.apache.jena.sparql.ARQException;
 
 public class FusekiException extends ARQException
 {
-    public FusekiException(String msg, Throwable cause)    { super(msg, cause) ; }
-    public FusekiException(String msg)                     { super(msg) ; }
-    public FusekiException(Throwable cause)                { super(cause) ; }
-    public FusekiException()                               { super() ; }
+    public FusekiException(String msg, Throwable cause)    { super(msg, cause); }
+    public FusekiException(String msg)                     { super(msg); }
+    public FusekiException(Throwable cause)                { super(cause); }
+    public FusekiException()                               { super(); }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncPool.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncPool.java
index 2cca42e..0161ba2 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncPool.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncPool.java
@@ -18,83 +18,83 @@
 
 package org.apache.jena.fuseki.async;
 
-import static java.lang.String.format ;
+import static java.lang.String.format;
 
-import java.util.* ;
-import java.util.concurrent.* ;
+import java.util.*;
+import java.util.concurrent.*;
 
-import org.apache.jena.fuseki.Fuseki ;
-import org.apache.jena.fuseki.server.DataService ;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.server.DataService;
 
 /** The set of currently active and recently completed tasks. */
 public class AsyncPool
 {
     // Max concurrent tasks.
-    private static int nMaxThreads = 4 ;
+    private static int nMaxThreads = 4;
     // Number of finished tasks kept.
-    private static int MAX_FINISHED = 20 ;
-    
-    // See Executors.newCachedThreadPool and Executors.newFixedThreadPool 
+    private static int MAX_FINISHED = 20;
+
+    // See Executors.newCachedThreadPool and Executors.newFixedThreadPool
     private ExecutorService executor = new ThreadPoolExecutor(0, nMaxThreads,
                                                               120L, TimeUnit.SECONDS,
-                                                              new LinkedBlockingQueue<Runnable>()) ;
-    private final Object mutex = new Object() ; 
-    private long counter = 0 ;
-    private Map<String, AsyncTask> runningTasks = new LinkedHashMap<>() ;
-    private Map<String, AsyncTask> finishedTasks = new LinkedHashMap<>() ;
-    // Finite FIFO of finished tasks. 
+                                                              new LinkedBlockingQueue<Runnable>());
+    private final Object mutex = new Object();
+    private long counter = 0;
+    private Map<String, AsyncTask> runningTasks = new LinkedHashMap<>();
+    private Map<String, AsyncTask> finishedTasks = new LinkedHashMap<>();
+    // Finite FIFO of finished tasks.
     private LinkedList<AsyncTask> finishedTasksList = new LinkedList<>();
-    
-    private static AsyncPool instance = new AsyncPool() ;
 
-    public static AsyncPool get() { return instance ; }
+    private static AsyncPool instance = new AsyncPool();
+
+    public static AsyncPool get() { return instance; }
 
     private AsyncPool() { }
-    
-    public AsyncTask submit(Runnable task, String displayName, DataService dataService, long requestId) { 
+
+    public AsyncTask submit(Runnable task, String displayName, DataService dataService, long requestId) {
         synchronized(mutex) {
-            String taskId = Long.toString(++counter) ;
-            Fuseki.serverLog.info(format("Task : %s : %s",taskId, displayName)) ;
+            String taskId = Long.toString(++counter);
+            Fuseki.serverLog.info(format("Task : %s : %s",taskId, displayName));
             Callable<Object> c = ()->{
-                try { task.run(); } 
+                try { task.run(); }
                 catch (Throwable th) {
                     Fuseki.serverLog.error(format("Exception in task %s execution", taskId), th);
                 }
-                return null; 
+                return null;
             };
-            AsyncTask asyncTask = new AsyncTask(c, this, taskId, displayName, dataService, requestId) ;
+            AsyncTask asyncTask = new AsyncTask(c, this, taskId, displayName, dataService, requestId);
             /* Future<Object> future = */ executor.submit(asyncTask);
-            runningTasks.put(taskId, asyncTask) ;
-            return asyncTask ;
+            runningTasks.put(taskId, asyncTask);
+            return asyncTask;
         }
     }
-    
+
     public Collection<AsyncTask> tasks() {
         synchronized(mutex) {
-            List<AsyncTask> x = new ArrayList<>(runningTasks.size()+finishedTasks.size()) ;
-            x.addAll(runningTasks.values()) ;
-            x.addAll(finishedTasks.values()) ;
-            return x ;
+            List<AsyncTask> x = new ArrayList<>(runningTasks.size()+finishedTasks.size());
+            x.addAll(runningTasks.values());
+            x.addAll(finishedTasks.values());
+            return x;
         }
     }
-    
-    public void finished(AsyncTask task) { 
+
+    public void finished(AsyncTask task) {
         synchronized(mutex) {
-            String id = task.getTaskId() ;
-            runningTasks.remove(id) ;
+            String id = task.getTaskId();
+            runningTasks.remove(id);
             // Reduce old tasks list
             while ( finishedTasksList.size() >= MAX_FINISHED ) {
                 AsyncTask oldTask = finishedTasksList.removeFirst();
                 finishedTasks.remove(oldTask.getTaskId());
             }
-            finishedTasks.put(id, task) ;
+            finishedTasks.put(id, task);
             finishedTasksList.add(task);
         }
     }
 
     public AsyncTask getRunningTask(String taskId) {
         synchronized(mutex) {
-            return runningTasks.get(taskId) ;
+            return runningTasks.get(taskId);
         }
     }
 
@@ -104,10 +104,10 @@ public class AsyncPool
      */
     public AsyncTask getTask(String taskId) {
         synchronized(mutex) {
-            AsyncTask task = runningTasks.get(taskId) ;
+            AsyncTask task = runningTasks.get(taskId);
             if ( task != null )
-                return task ;
-            return finishedTasks.get(taskId) ;
+                return task;
+            return finishedTasks.get(taskId);
         }
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncTask.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncTask.java
index b25f238..f224ed9 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncTask.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncTask.java
@@ -18,103 +18,103 @@
 
 package org.apache.jena.fuseki.async;
 
-import static java.lang.String.format ;
+import static java.lang.String.format;
 
-import java.util.concurrent.Callable ;
+import java.util.concurrent.Callable;
 
-import org.apache.jena.atlas.lib.DateTimeUtils ;
-import org.apache.jena.atlas.lib.InternalErrorException ;
-import org.apache.jena.atlas.logging.Log ;
-import org.apache.jena.fuseki.Fuseki ;
-import org.apache.jena.fuseki.server.DataService ;
-import org.slf4j.Logger ;
+import org.apache.jena.atlas.lib.DateTimeUtils;
+import org.apache.jena.atlas.lib.InternalErrorException;
+import org.apache.jena.atlas.logging.Log;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.server.DataService;
+import org.slf4j.Logger;
 
-/** An asynchronous task */ 
-public class AsyncTask implements Callable<Object>  
+/** An asynchronous task */
+public class AsyncTask implements Callable<Object>
 {
-    private static Logger log = Fuseki.serverLog ; 
-    
-    private final Callable<Object> callable ;
-    private final AsyncPool pool ;
+    private static Logger log = Fuseki.serverLog;
 
-    private final String displayName ;
-    private final DataService dataService ;
+    private final Callable<Object> callable;
+    private final AsyncPool pool;
 
-    private String startPoint = null ;
-    private String finishPoint = null ;
+    private final String displayName;
+    private final DataService dataService;
 
-    private final String taskId ;
+    private String startPoint = null;
+    private String finishPoint = null;
 
-    private long requestId ;
+    private final String taskId;
 
-    /*package*/ AsyncTask(Callable<Object> callable, 
+    private long requestId;
+
+    /*package*/ AsyncTask(Callable<Object> callable,
                           AsyncPool pool,
                           String taskId,
                           String displayName,
                           DataService dataService,
                           long requestId) {
-        this.callable = callable ;
-        this.pool = pool ;
-        this.taskId = taskId ; 
-        this.displayName = displayName ;
-        this.dataService = dataService ;
-        this.requestId = requestId ;
+        this.callable = callable;
+        this.pool = pool;
+        this.taskId = taskId;
+        this.displayName = displayName;
+        this.dataService = dataService;
+        this.requestId = requestId;
     }
 
     /** Unique task id */
-    public String getTaskId() { return taskId ; }
-    
+    public String getTaskId() { return taskId; }
+
     /** Request id that caused this task (may be -1 for N/A) */
-    public long getOriginatingRequestId() { return requestId ; }
+    public long getOriginatingRequestId() { return requestId; }
 
     /** Display name - no newlines */
-    public String displayName() { return displayName ; }
-    
-    public DataService getDataService() { return dataService ; }
+    public String displayName() { return displayName; }
+
+    public DataService getDataService() { return dataService; }
 
     private void start() {
         if ( startPoint != null ) {
-            String msg = format("[Task %s] Async task has already been started", taskId) ;
-            Log.warn(Fuseki.serverLog, msg) ;
-            throw new InternalErrorException("Finish has already been called ["+getTaskId()+"]") ; 
+            String msg = format("[Task %s] Async task has already been started", taskId);
+            Log.warn(Fuseki.serverLog, msg);
+            throw new InternalErrorException("Finish has already been called ["+getTaskId()+"]");
         }
-            
-        Fuseki.serverLog.info(format("[Task %s] starts : %s",taskId, displayName)) ;
-        startPoint = DateTimeUtils.nowAsXSDDateTimeString() ;
+
+        Fuseki.serverLog.info(format("[Task %s] starts : %s",taskId, displayName));
+        startPoint = DateTimeUtils.nowAsXSDDateTimeString();
     }
-    
+
     public void finish() {
         if ( finishPoint != null ) {
-            String msg = format("[Task %s] Async task has already been finished", taskId) ;
-            Log.warn(Fuseki.serverLog, msg) ;
-            throw new InternalErrorException("Finish has already been called ["+getTaskId()+"]") ; 
+            String msg = format("[Task %s] Async task has already been finished", taskId);
+            Log.warn(Fuseki.serverLog, msg);
+            throw new InternalErrorException("Finish has already been called ["+getTaskId()+"]");
         }
-        finishPoint = DateTimeUtils.nowAsXSDDateTimeString() ;
-        Fuseki.serverLog.info(format("[Task %s] finishes : %s",taskId, displayName)) ;
+        finishPoint = DateTimeUtils.nowAsXSDDateTimeString();
+        Fuseki.serverLog.info(format("[Task %s] finishes : %s",taskId, displayName));
     }
-    
+
     @Override
     public Object call() {
         try {
-            start() ;
-            return callable.call() ;
+            start();
+            return callable.call();
         }
         catch (Exception ex) {
-            log.error("Async task threw an expection", ex) ;
-            return null ;
+            log.error("Async task threw an expection", ex);
+            return null;
         }
         finally {
-            finish() ;
-            pool.finished(this) ;
+            finish();
+            pool.finished(this);
         }
     }
 
     public String getStartPoint() {
-        return startPoint ;
+        return startPoint;
     }
 
     public String getFinishPoint() {
-        return finishPoint ;
+        return finishPoint;
     }
 }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java
index c0b1d52..c163e11 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java
@@ -29,7 +29,7 @@ import org.apache.jena.fuseki.FusekiConfigException;
  * See {@link Users} for special user names.
  */
 public class Auth {
-    public static final String dftRealm = "TripleStore";   
+    public static final String dftRealm = "TripleStore";
 
     /** Any authenticated user. */
     public static AuthPolicy ANY_USER = (user) -> user != null;
@@ -40,18 +40,18 @@ public class Auth {
     /** Never allow. */
     public static AuthPolicy DENY     = (user) -> false;
 
-    /** A policy that allows specific users (convenience wrapped for {@link #policyAllowSpecific(Collection)}). */ 
+    /** A policy that allows specific users (convenience wrapped for {@link #policyAllowSpecific(Collection)}). */
     public static AuthPolicy policyAllowSpecific(String... allowedUsers) {
         return Auth.policyAllowSpecific(Arrays.asList(allowedUsers));
     }
-    
-    /** 
+
+    /**
      * A policy that allows specific users.
      * <ul>
      * <li>If any user is {@linkplain Users#UserAnyLoggedIn}, then this policy is the same as {@linkplain #ANY_USER}.
      * <li>If any user is {@linkplain Users#UserAnyAnon}, then this policy is the same as {@linkplain #ANY_ANON}.
      * </ul>
-     */ 
+     */
     public static AuthPolicy policyAllowSpecific(Collection<String> allowedUsers) {
         Objects.requireNonNull(allowedUsers, "allowedUsers");
         if ( allowedUsers.contains(Users.UserAnyLoggedIn) ) {
@@ -66,14 +66,16 @@ public class Auth {
         }
 
         if ( allowedUsers.stream().anyMatch(Objects::isNull) )
-            throw new FusekiConfigException("null user found : "+allowedUsers);  
+            throw new FusekiConfigException("null user found : "+allowedUsers);
+        if ( allowedUsers.isEmpty() )
+            return Auth.DENY;
         return new AuthUserList(allowedUsers);
     }
 
     /**
-     * Test whether a user (principal) is allowed by a authorization policy.  
+     * Test whether a user (principal) is allowed by a authorization policy.
      * The policy can be null, meaning no restrictions, and the function returns true.
-     * {@code user} maybe null, meaning unauthenticated and any policy must deal with this. 
+     * {@code user} maybe null, meaning unauthenticated and any policy must deal with this.
      * @param user
      * @param policy
      * @return boolean True if the policy is null or allows the user.
@@ -83,14 +85,14 @@ public class Auth {
             return true;
         return policy.isAllowed(user);
     }
-    
+
     /**
      * Test whether a user (principal) is allowed by a authorization policy
      * and perform an action if the policy does not allow the user.
      * The action can throw an exception.
      * Additional, return true/false - see {@link #allow(String, AuthPolicy)}.
      * The policy can be null, meaning no restrictions, and the function returns true.
-     * {@code user} maybe null, meaning unauthenticated and any policy must deal with this. 
+     * {@code user} maybe null, meaning unauthenticated and any policy must deal with this.
      * @param user
      * @param policy
      * @param notAllowed Runnable to execute if the policy does not allow the user.
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java
index 5a14025..a828833 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java
@@ -23,7 +23,7 @@ package org.apache.jena.fuseki.auth;
  * Assumes the user has already been authenticated.
  */
 public interface AuthPolicy {
-    /** 
+    /**
      * Is the use authorized for this resource?
      */
     public boolean isAllowed(String user);
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicyList.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicyList.java
index 1e00320..6dffcc8 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicyList.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicyList.java
@@ -21,16 +21,16 @@ package org.apache.jena.fuseki.auth;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
-/** 
+/**
  * An {@link AuthPolicy} that combines a number of {@link AuthPolicy AuthPolicies}.
  * All policies must authorize access for this policy to allow access.
- */ 
+ */
 public class AuthPolicyList implements AuthPolicy {
 
     // Thread safe.
-    // Use a 
+    // Use a
     private final Queue<AuthPolicy> policies = new ConcurrentLinkedQueue<>();
-    
+
     /**
      * Merge {@link AuthPolicy AuthPolicies}, returning a combination of the two if both are non-null.
      * If either is null, return the other.
@@ -38,7 +38,7 @@ public class AuthPolicyList implements AuthPolicy {
      */
     public static AuthPolicy merge(AuthPolicy policy1, AuthPolicy policy2) {
         if ( policy1 == null )
-            return policy2 ;
+            return policy2;
         if ( policy2 == null )
             return policy1;
         if ( policy1 instanceof AuthPolicyList) {
@@ -51,13 +51,13 @@ public class AuthPolicyList implements AuthPolicy {
         x.add(policy2);
         return x;
     }
-    
-    private AuthPolicyList(AuthPolicyList other) { 
+
+    private AuthPolicyList(AuthPolicyList other) {
         policies.addAll(other.policies);
     }
-    
+
     public AuthPolicyList() { }
-    
+
     public void add(AuthPolicy policy) {
         policies.add(policy);
     }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthUserList.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthUserList.java
index 55aae72..1b91bb6 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthUserList.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthUserList.java
@@ -21,7 +21,7 @@ package org.apache.jena.fuseki.auth;
 import java.util.*;
 
 /**
- * Policy for allowing users to execute a request. 
+ * Policy for allowing users to execute a request.
  * Assumes the user has been authenticated.
  */
 class AuthUserList implements AuthPolicy {
@@ -31,7 +31,7 @@ class AuthUserList implements AuthPolicy {
     /*package*/ AuthUserList(Collection<String> allowed) {
         this.allowedUsers = (allowed == null) ? Collections.emptySet() : new HashSet<>(allowed);
     }
-    
+
     @Override
     public boolean isAllowed(String user) {
         if ( user == null )
@@ -49,12 +49,12 @@ class AuthUserList implements AuthPolicy {
     static <T> boolean isNullOrEmpty(Collection<T> collection) {
         if ( collection == null )
             return true;
-        return collection.isEmpty(); 
+        return collection.isEmpty();
     }
-    
+
     static <T> boolean contains(Collection<T> collection, T obj) {
         if ( collection == null )
             return false;
-        return collection.contains(obj); 
+        return collection.contains(obj);
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Users.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Users.java
index 7b6c5fd..bf9aa2c 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Users.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Users.java
@@ -23,11 +23,11 @@ public class Users {
      * Reserved user role name: Name of the user role for any authenticated user of the system.
      * In the servlet API, this equates to {@code getRemoteUser() != null}.
      */
-    public static String UserAnyLoggedIn = "*"; 
+    public static String UserAnyLoggedIn = "*";
 
-    /** 
+    /**
      * Reserved user role name: Name of the user role for any authenticated user of the system
      * In the servlet API, this includes {@code getRemoteUser() == null}
      */
-    public static String UserAnyAnon = "_" ; 
+    public static String UserAnyAnon = "_";
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuildLib.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/BuildLib.java
similarity index 54%
rename from jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuildLib.java
rename to jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/BuildLib.java
index f2f71c4..dd061eb 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuildLib.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/BuildLib.java
@@ -18,32 +18,34 @@
 
 package org.apache.jena.fuseki.build;
 
+import static org.apache.jena.fuseki.build.FusekiPrefixes.PREFIXES;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
 import org.apache.jena.fuseki.FusekiConfigException;
-import org.apache.jena.query.* ;
+import org.apache.jena.query.*;
 import org.apache.jena.rdf.model.*;
 import org.apache.jena.shared.JenaException;
-import org.apache.jena.shared.PrefixMapping ;
-import org.apache.jena.vocabulary.RDFS ;
+import org.apache.jena.shared.PrefixMapping;
+import org.apache.jena.vocabulary.RDFS;
 
 /**
  * Library code for operations related to building Fuseki servers and services.
  */
-public class FusekiBuildLib {
+class BuildLib {
 
     // ---- Helper code
-    public static ResultSet query(String string, Model m) {
-        return query(string, m, null, null) ;
+    /*package*/ static ResultSet query(String string, Model m) {
+        return query(string, m, null, null);
     }
 
-    public static RDFNode queryOne(String string, Model m, String varname) {
-        ResultSet rs = query(string, m) ;
+    /*package*/ static RDFNode queryOne(String string, Model m, String varname) {
+        ResultSet rs = query(string, m);
         return getExactlyOne(rs, varname);
     }
-    
+
     private static RDFNode getExactlyOne(ResultSet rs, String varname) {
         if ( ! rs.hasNext() )
             return null;
@@ -53,49 +55,49 @@ public class FusekiBuildLib {
         return qs.get(varname);
     }
 
-    public static ResultSet query(String string, Dataset ds) {
-        return query(string, ds, null, null) ;
+    /*package*/ static ResultSet query(String string, Dataset ds) {
+        return query(string, ds, null, null);
     }
 
-    public static ResultSet query(String string, Model m, String varName, RDFNode value) {
-        Query query = QueryFactory.create(FusekiConst.PREFIXES + string) ;
-        QuerySolutionMap initValues = null ;
+    /*package*/ static ResultSet query(String string, Model m, String varName, RDFNode value) {
+        Query query = QueryFactory.create(PREFIXES + string);
+        QuerySolutionMap initValues = null;
         if ( varName != null && value != null )
-            initValues = querySolution(varName, value) ;
+            initValues = querySolution(varName, value);
         try ( QueryExecution qExec = QueryExecutionFactory.create(query, m, initValues) ) {
-            return ResultSetFactory.copyResults(qExec.execSelect()) ;
+            return ResultSetFactory.copyResults(qExec.execSelect());
         }
     }
 
-    public static ResultSet query(String string, Dataset ds, String varName, RDFNode value) {
-        Query query = QueryFactory.create(FusekiConst.PREFIXES + string) ;
-        QuerySolutionMap initValues = null ;
+    /*package*/ static ResultSet query(String string, Dataset ds, String varName, RDFNode value) {
+        Query query = QueryFactory.create(PREFIXES + string);
+        QuerySolutionMap initValues = null;
         if ( varName != null && value != null )
-            initValues = querySolution(varName, value) ;
+            initValues = querySolution(varName, value);
         try ( QueryExecution qExec = QueryExecutionFactory.create(query, ds, initValues) ) {
-            return ResultSetFactory.copyResults(qExec.execSelect()) ;
+            return ResultSetFactory.copyResults(qExec.execSelect());
         }
     }
 
     private static QuerySolutionMap querySolution(String varName, RDFNode value) {
-        QuerySolutionMap qsm = new QuerySolutionMap() ;
-        querySolution(qsm, varName, value) ;
-        return qsm ;
+        QuerySolutionMap qsm = new QuerySolutionMap();
+        querySolution(qsm, varName, value);
+        return qsm;
     }
 
-    public static QuerySolutionMap querySolution(QuerySolutionMap qsm, String varName, RDFNode value) {
-        qsm.add(varName, value) ;
-        return qsm ;
+    /*package*/ static QuerySolutionMap querySolution(QuerySolutionMap qsm, String varName, RDFNode value) {
+        qsm.add(varName, value);
+        return qsm;
     }
 
-    public static RDFNode getOne(Resource svc, String property) {
-        ResultSet rs = FusekiBuildLib.query("SELECT * { ?svc " + property + " ?x}", svc.getModel(), "svc", svc) ;
+    /*package*/ static RDFNode getOne(Resource svc, String property) {
+        ResultSet rs = BuildLib.query("SELECT * { ?svc " + property + " ?x}", svc.getModel(), "svc", svc);
         if ( !rs.hasNext() )
-            throw new FusekiConfigException("No property '" + property + "' for service " + FusekiBuildLib.nodeLabel(svc)) ;
-        RDFNode x = rs.next().get("x") ;
+            throw new FusekiConfigException("No property '" + property + "' for service " + BuildLib.nodeLabel(svc));
+        RDFNode x = rs.next().get("x");
         if ( rs.hasNext() )
-            throw new FusekiConfigException("Multiple properties '" + property + "' for service " + FusekiBuildLib.nodeLabel(svc)) ;
-        return x ;
+            throw new FusekiConfigException("Multiple properties '" + property + "' for service " + BuildLib.nodeLabel(svc));
+        return x;
     }
 
     /**
@@ -103,8 +105,8 @@ public class FusekiBuildLib {
      * mixture. If the subject/property isn't present, return null, so a caller can tell
      * the difference between "not present" and an empty list value.
      */
-    public static Collection<RDFNode> getAll(Resource resource, String property) {
-        ResultSet rs = FusekiBuildLib.query("SELECT * { ?subject " + property + " ?x}", resource.getModel(), "subject", resource) ;
+    /*package*/ static Collection<RDFNode> getAll(Resource resource, String property) {
+        ResultSet rs = BuildLib.query("SELECT * { ?subject " + property + " ?x}", resource.getModel(), "subject", resource);
         if ( ! rs.hasNext() )
             return null;
         List<RDFNode> results = new ArrayList<>();
@@ -118,49 +120,49 @@ public class FusekiBuildLib {
                 results.add(n);
             }
         });
-        return results ;
+        return results;
     }
 
     // Node presentation
-    public static String nodeLabel(RDFNode n) {
+    /*package*/ static String nodeLabel(RDFNode n) {
         if ( n == null )
-            return "<null>" ;
+            return "<null>";
         if ( n instanceof Resource )
-            return strForResource((Resource)n) ;
-    
-        Literal lit = (Literal)n ;
-        return lit.getLexicalForm() ;
+            return strForResource((Resource)n);
+
+        Literal lit = (Literal)n;
+        return lit.getLexicalForm();
     }
 
-    public static String strForResource(Resource r) {
-        return strForResource(r, r.getModel()) ;
+    /*package*/ static String strForResource(Resource r) {
+        return strForResource(r, r.getModel());
     }
 
-    public static String strForResource(Resource r, PrefixMapping pm) {
+    /*package*/ static String strForResource(Resource r, PrefixMapping pm) {
         if ( r == null )
-            return "NULL " ;
+            return "NULL ";
         if ( r.hasProperty(RDFS.label) ) {
-            RDFNode n = r.getProperty(RDFS.label).getObject() ;
+            RDFNode n = r.getProperty(RDFS.label).getObject();
             if ( n instanceof Literal )
-                return ((Literal)n).getString() ;
+                return ((Literal)n).getString();
         }
-    
+
         if ( r.isAnon() )
-            return "<<blank node>>" ;
-    
+            return "<<blank node>>";
+
         if ( pm == null )
-            pm = r.getModel() ;
-    
-        return strForURI(r.getURI(), pm) ;
+            pm = r.getModel();
+
+        return strForURI(r.getURI(), pm);
     }
 
-    public static String strForURI(String uri, PrefixMapping pm) {
+    /*package*/ static String strForURI(String uri, PrefixMapping pm) {
         if ( pm != null ) {
-            String x = pm.shortForm(uri) ;
-    
+            String x = pm.shortForm(uri);
+
             if ( !x.equals(uri) )
-                return x ;
+                return x;
         }
-        return "<" + uri + ">" ;
+        return "<" + uri + ">";
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DatasetDescriptionRegistry.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DatasetDescriptionMap.java
similarity index 74%
rename from jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DatasetDescriptionRegistry.java
rename to jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DatasetDescriptionMap.java
index b49c5be..c814a8f 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DatasetDescriptionRegistry.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DatasetDescriptionMap.java
@@ -21,30 +21,26 @@ package org.apache.jena.fuseki.build;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.jena.atlas.logging.Log ;
+import org.apache.jena.atlas.logging.Log;
 import org.apache.jena.query.Dataset;
 import org.apache.jena.rdf.model.Resource;
 
 /**
- * Registry of Datasets created from descriptions.
- * 
- * <p>
- *   Provides a registry for use in building the Fuseki configuration to
- *   ensure that each dataset description resource in configuration graphs
- *   corresponds to one dataset object when multiple services refer to the
- *   same dataset.
- * </p>
- * 
+ * Record of datasets created from descriptions.
  *
+ * Provides a registry for use in building one Fuseki configuration to
+ * ensure that each dataset description resource in configuration graphs
+ * corresponds to one dataset object when multiple services refer to the
+ * same dataset.
  */
-public class DatasetDescriptionRegistry  {
+public class DatasetDescriptionMap  {
 	
 	private Map<Resource, Dataset> map = new HashMap<>();
 	
-	public DatasetDescriptionRegistry() {}
+	public DatasetDescriptionMap() {}
 	
     public void register(Resource node, Dataset ds) {
-        Dataset dsCurrent = map.get(node) ;
+        Dataset dsCurrent = map.get(node);
         if ( dsCurrent != null ) {
             if ( ! dsCurrent.equals(ds) )
                 Log.warn(this.getClass(), "Replacing registered dataset for "+node);
@@ -55,7 +51,7 @@ public class DatasetDescriptionRegistry  {
     public Dataset get(Resource node) {
         return map.get(node);
     }
-    
+
     public void clear() {
         map.clear();
     }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuilder.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuilder.java
deleted file mode 100644
index dfeb8fd..0000000
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuilder.java
+++ /dev/null
@@ -1,172 +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.build;
-
-import static java.lang.String.format;
-import static java.util.stream.Collectors.toList;
-import static org.apache.jena.fuseki.server.FusekiVocab.pAllowedUsers;
-
-import java.util.Collection;
-import java.util.List;
-
-import org.apache.jena.fuseki.FusekiConfigException;
-import org.apache.jena.fuseki.auth.Auth;
-import org.apache.jena.fuseki.auth.AuthPolicy;
-import org.apache.jena.fuseki.server.*;
-import org.apache.jena.graph.Node;
-import org.apache.jena.query.QuerySolution ;
-import org.apache.jena.query.ResultSet ;
-import org.apache.jena.rdf.model.*;
-import org.apache.jena.rdf.model.impl.Util;
-import org.apache.jena.shared.JenaException;
-import org.apache.jena.sparql.core.DatasetGraph ;
-import org.apache.jena.sparql.util.graph.GraphUtils;
-
-/**
- * Helper functions use to construct Fuseki servers.
- * @see FusekiConfig
- */
-public class FusekiBuilder
-{
-    /** Build a DataService starting at Resource svc, with the standard (default) set of services. */
-    public static DataService buildDataServiceStd(DatasetGraph dsg, boolean allowUpdate) {
-        DataService dataService = new DataService(dsg) ;
-        populateStdServices(dataService, allowUpdate);
-        return dataService ;
-    }        
-        
-    /** Convenience operation to populate a {@link DataService} with the conventional default services. */ 
-    public static void populateStdServices(DataService dataService, boolean allowUpdate) {
-        addServiceEP(dataService, Operation.Query,      "query") ;
-        addServiceEP(dataService, Operation.Query,      "sparql") ;
-        if ( ! allowUpdate ) {
-            addServiceEP(dataService, Operation.GSP_R,      "data") ;
-            addServiceEP(dataService, Operation.DatasetRequest_R,    "") ;
-            return;
-        }
-        addServiceEP(dataService, Operation.GSP_RW,     "data") ;
-        addServiceEP(dataService, Operation.GSP_R,      "get") ;
-        addServiceEP(dataService, Operation.Update,     "update") ;
-        addServiceEP(dataService, Operation.Upload,     "upload") ;
-        addServiceEP(dataService, Operation.DatasetRequest_RW, "") ;
-    }
-
-    /** Add an operation to a {@link DataService} with a given endpoint name */
-    public static void addServiceEP(DataService dataService, Operation operation, String endpointName) {
-        dataService.addEndpoint(operation, endpointName) ;
-    }
-
-    /** Add an operation to a {@link DataService} with a given endpoint name */
-    public static void addServiceEP(DataService dataService, Operation operation, String endpointName, AuthPolicy requestAuth) {
-        dataService.addEndpoint(operation, endpointName, requestAuth) ;
-    }
-
-    public static void addServiceEP(DataService dataService, Operation operation, Resource svc, Property property) {
-        String p = "<"+property.getURI()+">" ;
-        ResultSet rs = FusekiBuildLib.query("SELECT * { ?svc " + p + " ?ep}", svc.getModel(), "svc", svc) ;
-        for ( ; rs.hasNext() ; ) {
-            QuerySolution soln = rs.next() ;
-            // No policy yet - set below if one is found.
-            AuthPolicy requestAuth = null;
-            RDFNode ep = soln.get("ep");
-            String epName = null;
-            if ( ep.isLiteral() )
-                epName = soln.getLiteral("ep").getLexicalForm() ;
-            else if ( ep.isResource() ) {
-                Resource r = (Resource)ep;
-                try {
-                    // Look for possible:
-                    // [ fuseki:name "" ; fuseki:allowedUsers ( "" "" ) ]
-                    epName = r.getProperty(FusekiVocab.pServiceName).getString();
-                    List<RDFNode> x = GraphUtils.multiValue(r, FusekiVocab.pAllowedUsers);
-                    if ( x.size() > 1 )
-                        throw new FusekiConfigException("Multiple fuseki:"+FusekiVocab.pAllowedUsers.getLocalName()+" for "+r); 
-                    if ( ! x.isEmpty() )
-                        requestAuth = FusekiBuilder.allowedUsers(r);
-                } catch(JenaException | ClassCastException ex) {
-                    throw new FusekiConfigException("Failed to parse endpoint: "+r);
-                }
-            } else {
-                throw new FusekiConfigException("Unrecognized: "+ep);
-            }
-            addServiceEP(dataService, operation, epName, requestAuth); 
-            //log.info("  " + operation.name + " = " + dataAccessPoint.getName() + "/" + epName) ;
-        }
-    }
-    
-    public static void addDataService(DataAccessPointRegistry dataAccessPoints, String name, DataService dataService) {
-        name = DataAccessPoint.canonical(name);
-        if ( dataAccessPoints.isRegistered(name) )
-            throw new FusekiConfigException("Data service name already registered: "+name);
-        DataAccessPoint dap = new DataAccessPoint(name, dataService);
-        dataAccessPoints.register(dap);
-    }
-    
-    public static void addDataset(DataAccessPointRegistry dataAccessPoints, String name, DatasetGraph dsg, boolean withUpdate) {
-        name = DataAccessPoint.canonical(name);
-        if ( dataAccessPoints.isRegistered(name) )
-            throw new FusekiConfigException("Data service name already registered: "+name);
-        DataAccessPoint dap = buildDataAccessPoint(name, dsg, withUpdate);
-        dataAccessPoints.register(dap);
-    }
-    
-    private static DataAccessPoint buildDataAccessPoint(String name, DatasetGraph dsg, boolean withUpdate) { 
-        // See Builder. DRY.
-        DataService dataService = FusekiBuilder.buildDataServiceStd(dsg, withUpdate);
-        DataAccessPoint dap = new DataAccessPoint(name, dataService);
-        return dap;
-    }
-    
-    public static void removeDataset(DataAccessPointRegistry dataAccessPoints, String name) {
-        name = DataAccessPoint.canonical(name);
-        dataAccessPoints.remove(name);
-    }
-
-    /** Get the allowed users on a resource.
-     *  Returns null if the resource is null or if there were no settings. 
-     *  
-     * @param resource
-     * @return RequestAuthorization
-     */
-    public static AuthPolicy allowedUsers(Resource resource) {
-        if ( resource == null )
-            return null;
-        Collection<RDFNode> allowedUsers = FusekiBuildLib.getAll(resource, "fu:"+pAllowedUsers.getLocalName());
-        if ( allowedUsers == null )
-            // Indicate no settings.
-            return null;
-        // Check all values are simple strings  
-        List<String> bad = allowedUsers.stream()
-            .map(RDFNode::asNode)
-            .filter(rn -> ! Util.isSimpleString(rn))
-            .map(rn->rn.toString())
-            .collect(toList());
-        if ( ! bad.isEmpty() ) {
-            //Fuseki.configLog.error(format("User names must be a simple string: bad = %s", bad));
-            throw new FusekiConfigException(format("User names should be a simple string: bad = %s", bad));
-        }
-        // RDFNodes/literals to strings.
-        Collection<String> userNames = allowedUsers.stream()
-            .map(RDFNode::asNode)
-            .map(Node::getLiteralLexicalForm)
-            .collect(toList());
-        return Auth.policyAllowSpecific(userNames);
-    }
-}
-
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 b427f86..f790dd3 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
@@ -16,56 +16,173 @@
  * limitations under the License.
  */
 
-package org.apache.jena.fuseki.build ;
+package org.apache.jena.fuseki.build;
 
 import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
 import static org.apache.jena.fuseki.server.FusekiVocab.*;
 import static org.apache.jena.riot.RDFLanguages.filenameToLang;
 import static org.apache.jena.riot.RDFParserRegistry.isRegistered;
 
-import java.io.File ;
-import java.io.IOException ;
-import java.lang.reflect.Method ;
-import java.nio.file.DirectoryStream ;
-import java.nio.file.Files ;
-import java.nio.file.Path ;
-import java.nio.file.Paths ;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.jena.assembler.Assembler;
-import org.apache.jena.assembler.JA ;
-import org.apache.jena.atlas.lib.IRILib ;
-import org.apache.jena.atlas.lib.StrUtils ;
+import org.apache.jena.assembler.JA;
+import org.apache.jena.atlas.lib.IRILib;
+import org.apache.jena.atlas.lib.StrUtils;
 import org.apache.jena.datatypes.xsd.XSDDatatype;
-import org.apache.jena.fuseki.Fuseki ;
-import org.apache.jena.fuseki.FusekiConfigException ;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.FusekiConfigException;
+import org.apache.jena.fuseki.auth.Auth;
 import org.apache.jena.fuseki.auth.AuthPolicy;
 import org.apache.jena.fuseki.server.*;
-import org.apache.jena.query.Dataset ;
-import org.apache.jena.query.QuerySolution ;
-import org.apache.jena.query.ReadWrite ;
-import org.apache.jena.query.ResultSet ;
+import org.apache.jena.graph.Node;
+import org.apache.jena.query.*;
 import org.apache.jena.rdf.model.*;
+import org.apache.jena.rdf.model.impl.Util;
 import org.apache.jena.riot.Lang;
-import org.apache.jena.sparql.core.assembler.AssemblerUtils ;
+import org.apache.jena.shared.JenaException;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.assembler.AssemblerUtils;
 import org.apache.jena.sparql.util.Context;
 import org.apache.jena.sparql.util.FmtUtils;
 import org.apache.jena.sparql.util.graph.GraphUtils;
 import org.apache.jena.vocabulary.RDF;
-import org.slf4j.Logger ;
+import org.slf4j.Logger;
 
+/** Functions to setup and act onthe configuration of a Fuseki server */  
 public class FusekiConfig {
-    static { Fuseki.init() ; }
-    
-    private static Logger log = Fuseki.configLog ;
+    static { Fuseki.init(); }
+
+    private static Logger log = Fuseki.configLog;
     
+    /** Build a DataService starting at Resource svc, with the standard (default) set of services. */
+    public static DataService buildDataServiceStd(DatasetGraph dsg, boolean allowUpdate) {
+        DataService dataService = new DataService(dsg);
+        populateStdServices(dataService, allowUpdate);
+        return dataService;
+    }
+
+    /** Convenience operation to populate a {@link DataService} with the conventional default services. */
+    public static void populateStdServices(DataService dataService, boolean allowUpdate) {
+        Set<Endpoint> endpoints = new HashSet<>();
+        
+        accEndpoint(endpoints, Operation.Query,      "query");
+        accEndpoint(endpoints, Operation.Query,      "sparql");
+        if ( ! allowUpdate ) {
+            accEndpoint(endpoints, Operation.GSP_R,      "data");
+        } else {
+            accEndpoint(endpoints, Operation.GSP_RW,     "data");
+            accEndpoint(endpoints, Operation.GSP_R,      "get");
+            accEndpoint(endpoints, Operation.Update,     "update");
+            accEndpoint(endpoints, Operation.Upload,     "upload");
+        }
+        // Dataset
+        accEndpoint(endpoints, Operation.Query);
+        accEndpoint(endpoints, Operation.GSP_R);
+        if ( allowUpdate ) {
+            accEndpoint(endpoints, Operation.Update);
+            accEndpoint(endpoints, Operation.GSP_RW);
+        }
+
+        // Add to DataService.
+        endpoints.forEach(dataService::addEndpoint);
+    }
+
+    /** Add an operation to a {@link DataService} for the dataset. */
+    public static void addDatasetEP(DataService dataService, Operation operation) {
+        addDatasetEP(dataService, operation, null);
+    }
+
+    /** Add an operation to a {@link DataService} for the dataset. */
+    public static void addDatasetEP(DataService dataService, Operation operation, AuthPolicy authPolicy) {
+        dataService.addEndpointNoName(operation, authPolicy);
+    }
+
+    /** Add an operation to a {@link DataService} with a given endpoint name */
+    public static void addServiceEP(DataService dataService, Operation operation, String endpointName) {
+        addServiceEP(dataService, operation, endpointName, null);
+    }
+
+    /** Add an operation to a {@link DataService} with a given endpoint name */
+    public static void addServiceEP(DataService dataService, Operation operation, String endpointName, AuthPolicy authPolicy) {
+        if ( StringUtils.isEmpty(endpointName) )
+            dataService.addEndpointNoName(operation, authPolicy);
+        else
+            dataService.addEndpoint(operation, endpointName, authPolicy);
+    }
+
+    public static void addDataService(DataAccessPointRegistry dataAccessPoints, String name, DataService dataService) {
+        name = DataAccessPoint.canonical(name);
+        if ( dataAccessPoints.isRegistered(name) )
+            throw new FusekiConfigException("Data service name already registered: "+name);
+        DataAccessPoint dap = new DataAccessPoint(name, dataService);
+        dataAccessPoints.register(dap);
+    }
+
+    public static void addDataset(DataAccessPointRegistry dataAccessPoints, String name, DatasetGraph dsg, boolean withUpdate) {
+        name = DataAccessPoint.canonical(name);
+        if ( dataAccessPoints.isRegistered(name) )
+            throw new FusekiConfigException("Data service name already registered: "+name);
+        DataAccessPoint dap = buildDataAccessPoint(name, dsg, withUpdate);
+        dataAccessPoints.register(dap);
+    }
+
+    private static DataAccessPoint buildDataAccessPoint(String name, DatasetGraph dsg, boolean withUpdate) {
+        // See Builder. DRY.
+        DataService dataService = buildDataServiceStd(dsg, withUpdate);
+        DataAccessPoint dap = new DataAccessPoint(name, dataService);
+        return dap;
+    }
+
+    public static void removeDataset(DataAccessPointRegistry dataAccessPoints, String name) {
+        name = DataAccessPoint.canonical(name);
+        dataAccessPoints.remove(name);
+    }
+
+    /** Get the allowed users on a resource.
+     *  Returns null if the resource is null or if there were no settings.
+     *
+     * @param resource
+     * @return RequestAuthorization
+     */
+    public static AuthPolicy allowedUsers(Resource resource) {
+        if ( resource == null )
+            return null;
+        Collection<RDFNode> allowedUsers = BuildLib.getAll(resource, "fu:"+pAllowedUsers.getLocalName());
+        if ( allowedUsers == null )
+            // Indicate no settings.
+            return null;
+        // Check all values are simple strings
+        List<String> bad = allowedUsers.stream()
+            .map(RDFNode::asNode)
+            .filter(rn -> ! Util.isSimpleString(rn))
+            .map(rn->rn.toString())
+            .collect(toList());
+        if ( ! bad.isEmpty() ) {
+            //Fuseki.configLog.error(format("User names must be a simple string: bad = %s", bad));
+            throw new FusekiConfigException(format("User names should be a simple string: bad = %s", bad));
+        }
+        // RDFNodes/literals to strings.
+        Collection<String> userNames = allowedUsers.stream()
+            .map(RDFNode::asNode)
+            .map(Node::getLiteralLexicalForm)
+            .collect(toList());
+        return Auth.policyAllowSpecific(userNames);
+    }
+
     /**
-     * Process a configuration file and return the {@link DataAccessPoint
-     * DataAccessPoints}; set the context provided for server-wide settings.
-     * 
+     * Process a configuration file and return the {@link DataAccessPoint DataAccessPoints};
+     * set the context provided for server-wide settings.
+     *
      * This bundles together the steps:
      * <ul>
      * <li>{@link #findServer}
@@ -73,13 +190,13 @@ public class FusekiConfig {
      * <li>{@link #processLoadClass} (legacy)
      * <li>{@link #servicesAndDatasets}
      * </ul>
-     */ 
+     */
     public static List<DataAccessPoint> processServerConfiguration(Model configuration, Context context) {
-        Resource server = FusekiConfig.findServer(configuration);
-        FusekiConfig.processContext(server, context);
-        FusekiConfig.processLoadClass(server);
+        Resource server = findServer(configuration);
+        processContext(server, context);
+        processLoadClass(server);
         // Process services, whether via server ja:services or, if absent, by finding by type.
-        List<DataAccessPoint> x = FusekiConfig.servicesAndDatasets(configuration);
+        List<DataAccessPoint> x = servicesAndDatasets(configuration);
         return x;
     }
 
@@ -87,7 +204,7 @@ public class FusekiConfig {
      * Process a configuration file, starting {@code server}.
      * Return the {@link DataAccessPoint DataAccessPoints}
      * set the context provided for server-wide settings.
-     * 
+     *
      * This bundles together the steps:
      * <ul>
      * <li>{@link #findServer}
@@ -95,13 +212,13 @@ public class FusekiConfig {
      * <li>{@link #processLoadClass} (legacy)
      * <li>{@link #servicesAndDatasets}
      * </ul>
-     */ 
+     */
     public static List<DataAccessPoint> processServerConfiguration(Resource server, Context context) {
         Objects.requireNonNull(server);
-        FusekiConfig.processContext(server, context);
-        FusekiConfig.processLoadClass(server);
+        processContext(server, context);
+        processLoadClass(server);
         // Process services, whether via server ja:services or, if absent, by finding by type.
-        List<DataAccessPoint> x = FusekiConfig.servicesAndDatasets(server);
+        List<DataAccessPoint> x = servicesAndDatasets(server);
         return x;
     }
 
@@ -111,248 +228,288 @@ public class FusekiConfig {
      * Raises {@link FusekiConfigException} is there are more than one.
      */
     public static Resource findServer(Model model) {
-        List<Resource> servers = GraphUtils.listResourcesByType(model, FusekiVocab.tServer) ;
+        List<Resource> servers = GraphUtils.listResourcesByType(model, FusekiVocab.tServer);
         if ( servers.size() == 0 )
             // "No server" is fine.
             return null;
         if ( servers.size() > 1 )
             throw new FusekiConfigException(servers.size()
-                                            + " servers found (must be exactly one in a configuration file)") ;
+                                            + " servers found (must be exactly one in a configuration file)");
         // ---- Server
-        Resource server = servers.get(0) ;
-        return server ; 
+        Resource server = servers.get(0);
+        return server;
     }
-    
+
     /**
-     * Process the configuration file declarations for {@link Context} settings.   
+     * Process the configuration file declarations for {@link Context} settings.
      */
     public static void processContext(Resource server, Context cxt) {
         if ( server == null )
-            return ;
-        AssemblerUtils.setContext(server, cxt) ;
+            return;
+        AssemblerUtils.setContext(server, cxt);
     }
-    
+
     /**
      * Process any {@code ja:loadClass}
      */
     public static void processLoadClass(Resource server) {
         if ( server == null )
-            return ;
-        StmtIterator sIter = server.listProperties(JA.loadClass) ;
-        for ( ; sIter.hasNext() ; ) {
-            Statement s = sIter.nextStatement() ;
-            RDFNode rn = s.getObject() ;
-            String className = null ;
+            return;
+        StmtIterator sIter = server.listProperties(JA.loadClass);
+        for (; sIter.hasNext(); ) {
+            Statement s = sIter.nextStatement();
+            RDFNode rn = s.getObject();
+            String className = null;
             if ( rn instanceof Resource ) {
-                String uri = ((Resource)rn).getURI() ;
+                String uri = ((Resource)rn).getURI();
                 if ( uri == null ) {
-                    log.warn("Blank node for class to load") ;
-                    continue ;
+                    log.warn("Blank node for class to load");
+                    continue;
                 }
-                String javaScheme = "java:" ;
+                String javaScheme = "java:";
                 if ( !uri.startsWith(javaScheme) ) {
-                    log.warn("Class to load is not 'java:': " + uri) ;
-                    continue ;
+                    log.warn("Class to load is not 'java:': " + uri);
+                    continue;
                 }
-                className = uri.substring(javaScheme.length()) ;
+                className = uri.substring(javaScheme.length());
             }
             if ( rn instanceof Literal )
-                className = ((Literal)rn).getLexicalForm() ;
-            /* Loader. */loadAndInit(className) ;
+                className = ((Literal)rn).getLexicalForm();
+            /* Loader. */loadAndInit(className);
         }
     }
-    
+
     /** Find and process datasets and services in a configuration file.
      * This can be a Fuseki server configuration file or a services-only configuration file.
-     * It looks {@code fuseki:services ( .... )} then, if not found, all {@code rtdf:type fuseki:services}. 
+     * It looks {@code fuseki:services ( .... )} then, if not found, all {@code rtdf:type fuseki:services}.
      * @see #processServerConfiguration
      */
     public static List<DataAccessPoint> servicesAndDatasets(Model model) {
         Resource server = findServer(model);
         return servicesAndDatasets$(server, model);
     }
-    
+
     /** Find and process datasets and services in a configuration file
      * starting from {@code server} which can have a {@code fuseki:services ( .... )}
-     * but, if not found, all {@code rtdf:type fuseki:services} are processed. 
+     * but, if not found, all {@code rtdf:type fuseki:services} are processed.
      */
     public static List<DataAccessPoint> servicesAndDatasets(Resource server) {
         Objects.requireNonNull(server);
         return servicesAndDatasets$(server, server.getModel());
-    }   
-    
-    private static List<DataAccessPoint> servicesAndDatasets$(Resource server, Model model) {        
-        DatasetDescriptionRegistry dsDescMap = new DatasetDescriptionRegistry();
+    }
+
+    private static List<DataAccessPoint> servicesAndDatasets$(Resource server, Model model) {
+        DatasetDescriptionMap dsDescMap = new DatasetDescriptionMap();
         // ---- Services
         // Server to services.
-        ResultSet rs = FusekiBuildLib.query("SELECT * { ?s fu:services [ list:member ?service ] }", model, "s", server) ;
-        List<DataAccessPoint> accessPoints = new ArrayList<>() ;
+        ResultSet rs = BuildLib.query("SELECT * { ?s fu:services [ list:member ?service ] }", model, "s", server);
+        List<DataAccessPoint> accessPoints = new ArrayList<>();
 
         // If none, look for services by type.
         if ( ! rs.hasNext() )
             // No "fu:services ( .... )" so try looking for services directly.
-            // This means Fuseki2, service configuration files (no server section) work for --conf. 
-            rs = FusekiBuildLib.query("SELECT ?service { ?service a fu:Service }", model) ;
+            // This means Fuseki2, service configuration files (no server section) work for --conf.
+            rs = BuildLib.query("SELECT ?service { ?service a fu:Service }", model);
 
         // rs is a result set of services to process.
-        for ( ; rs.hasNext() ; ) {
-            QuerySolution soln = rs.next() ;
-            Resource svc = soln.getResource("service") ;
-            DataAccessPoint acc = buildDataAccessPoint(svc, dsDescMap) ;
-            accessPoints.add(acc) ;
+        for (; rs.hasNext(); ) {
+            QuerySolution soln = rs.next();
+            Resource svc = soln.getResource("service");
+            DataAccessPoint acc = buildDataAccessPoint(svc, dsDescMap);
+            accessPoints.add(acc);
         }
-        return accessPoints ;
+        return accessPoints;
     }
-    
+
     private static void loadAndInit(String className) {
         try {
-            Class<? > classObj = Class.forName(className) ;
-            log.info("Loaded " + className) ;
-            Method initMethod = classObj.getMethod("init") ;
-            initMethod.invoke(null) ;
+            Class<? > classObj = Class.forName(className);
+            log.info("Loaded " + className);
+            Method initMethod = classObj.getMethod("init");
+            initMethod.invoke(null);
         }
         catch (ClassNotFoundException ex) {
-            log.warn("Class not found: " + className) ;
+            log.warn("Class not found: " + className);
         }
         catch (Exception e) {
-            throw new FusekiConfigException(e) ;
+            throw new FusekiConfigException(e);
         }
     }
-    
+
     private static Model readAssemblerFile(String filename) {
-        return AssemblerUtils.readAssemblerFile(filename) ;
+        return AssemblerUtils.readAssemblerFile(filename);
     }
-    
+
     // ---- Directory of assemblers
-    
-    /** Read service descriptions in the given directory */ 
+
+    /** Read service descriptions in the given directory */
     public static List<DataAccessPoint> readConfigurationDirectory(String dir) {
-        Path pDir = Paths.get(dir).normalize() ;
-        File dirFile = pDir.toFile() ;
+        Path pDir = Paths.get(dir).normalize();
+        File dirFile = pDir.toFile();
         if ( ! dirFile.exists() ) {
-            log.warn("Not found: directory for assembler files for services: '"+dir+"'") ;
-            return Collections.emptyList() ;
+            log.warn("Not found: directory for assembler files for services: '"+dir+"'");
+            return Collections.emptyList();
         }
         if ( ! dirFile.isDirectory() ) {
-            log.warn("Not a directory: '"+dir+"'") ;
-            return Collections.emptyList() ;
+            log.warn("Not a directory: '"+dir+"'");
+            return Collections.emptyList();
         }
         // Files that are not hidden.
         DirectoryStream.Filter<Path> filter = (entry)-> {
-            File f = entry.toFile() ;
+            File f = entry.toFile();
             final Lang lang = filenameToLang(f.getName());
-            return ! f.isHidden() && f.isFile() && lang != null && isRegistered(lang) ;
-        } ;
+            return ! f.isHidden() && f.isFile() && lang != null && isRegistered(lang);
+        };
 
-        List<DataAccessPoint> dataServiceRef = new ArrayList<>() ;
+        List<DataAccessPoint> dataServiceRef = new ArrayList<>();
         try (DirectoryStream<Path> stream = Files.newDirectoryStream(pDir, filter)) {
             for ( Path p : stream ) {
-                DatasetDescriptionRegistry dsDescMap = new DatasetDescriptionRegistry() ;
-                String fn = IRILib.filenameToIRI(p.toString()) ;
+                DatasetDescriptionMap dsDescMap = new DatasetDescriptionMap();
+                String fn = IRILib.filenameToIRI(p.toString());
                 log.info("Load configuration: "+fn);
-                Model m = readAssemblerFile(fn) ;
-                readConfiguration(m, dsDescMap, dataServiceRef) ; 
+                Model m = readAssemblerFile(fn);
+                readConfiguration(m, dsDescMap, dataServiceRef);
             }
         } catch (IOException ex) {
             log.warn("IOException:"+ex.getMessage(), ex);
         }
-        return dataServiceRef ;
+        return dataServiceRef;
     }
 
     /** Read a configuration in a model.
      * Allow dataset descriptions to be carried over from another place.
-     * Add to a list. 
+     * Add to a list.
      */
-    private static void readConfiguration(Model m, DatasetDescriptionRegistry dsDescMap, List<DataAccessPoint> dataServiceRef) {
-        List<Resource> services = GraphUtils.listResourcesByType(m, FusekiVocab.fusekiService) ; 
+    private static void readConfiguration(Model m, DatasetDescriptionMap dsDescMap, List<DataAccessPoint> dataServiceRef) {
+        List<Resource> services = GraphUtils.listResourcesByType(m, FusekiVocab.fusekiService);
 
         if ( services.size() == 0 ) {
-            log.error("No services found") ;
-            throw new FusekiConfigException() ;
+            log.error("No services found");
+            throw new FusekiConfigException();
         }
 
         for ( Resource service : services ) {
-            DataAccessPoint acc = buildDataAccessPoint(service, dsDescMap) ; 
-            dataServiceRef.add(acc) ;
+            DataAccessPoint acc = buildDataAccessPoint(service, dsDescMap);
+            dataServiceRef.add(acc);
         }
     }
-    
-    /** Build a DataAccessPoint, including DataService, from the description at Resource svc */ 
-    public static DataAccessPoint buildDataAccessPoint(Resource svc, DatasetDescriptionRegistry dsDescMap) {
-        RDFNode n = FusekiBuildLib.getOne(svc, "fu:name") ;
+
+    /** Build a DataAccessPoint, including DataService, from the description at Resource svc */
+    public static DataAccessPoint buildDataAccessPoint(Resource svc, DatasetDescriptionMap dsDescMap) {
+        RDFNode n = BuildLib.getOne(svc, "fu:name");
         if ( ! n.isLiteral() )
             throw new FusekiConfigException("Not a literal for access point name: "+FmtUtils.stringForRDFNode(n));
-        Literal object = n.asLiteral() ;
-        
+        Literal object = n.asLiteral();
+
         if ( object.getDatatype() != null && ! object.getDatatype().equals(XSDDatatype.XSDstring) )
             Fuseki.configLog.error(format("Service name '%s' is not a string", FmtUtils.stringForRDFNode(object)));
 
-        String name = object.getLexicalForm() ;
-        name = DataAccessPoint.canonical(name) ;
-        DataService dataService = buildDataService(svc, dsDescMap) ;
-        AuthPolicy allowedUsers = FusekiBuilder.allowedUsers(svc);
+        String name = object.getLexicalForm();
+        name = DataAccessPoint.canonical(name);
+        DataService dataService = buildDataService(svc, dsDescMap);
+        AuthPolicy allowedUsers = allowedUsers(svc);
         dataService.setAuthPolicy(allowedUsers);
-        DataAccessPoint dataAccess = new DataAccessPoint(name, dataService) ;
-        return dataAccess ;
+        DataAccessPoint dataAccess = new DataAccessPoint(name, dataService);
+        return dataAccess;
     }
-    
+
     /** Build a DatasetRef starting at Resource svc, having the services as described by the descriptions. */
-    private static DataService buildDataService(Resource svc, DatasetDescriptionRegistry dsDescMap) {
-        Resource datasetDesc = ((Resource)FusekiBuildLib.getOne(svc, "fu:dataset")) ;
-        
+    private static DataService buildDataService(Resource svc, DatasetDescriptionMap dsDescMap) {
+        Resource datasetDesc = ((Resource)BuildLib.getOne(svc, "fu:dataset"));
+
         Dataset ds = getDataset(datasetDesc, dsDescMap);
- 
-        // In case the assembler included ja:contents
-        DataService dataService = new DataService(ds.asDatasetGraph()) ;
-
-        FusekiBuilder.addServiceEP(dataService, Operation.Query,  svc,    pServiceQueryEP) ;
-        FusekiBuilder.addServiceEP(dataService, Operation.Update, svc,    pServiceUpdateEP) ;
-        FusekiBuilder.addServiceEP(dataService, Operation.Upload, svc,    pServiceUploadEP);
-        FusekiBuilder.addServiceEP(dataService, Operation.GSP_R,  svc,    pServiceReadGraphStoreEP) ;
-        FusekiBuilder.addServiceEP(dataService, Operation.GSP_RW, svc,    pServiceReadWriteGraphStoreEP) ;
-
-        FusekiBuilder.addServiceEP(dataService, Operation.Quads_R, svc,   pServiceReadQuadsEP) ;
-        FusekiBuilder.addServiceEP(dataService, Operation.Quads_RW, svc,  pServiceReadWriteQuadsEP) ;
-        
-        // Quads - actions directly on the dataset URL are different.
-        // In the config file they are also implicit when using GSP.
+        DataService dataService = new DataService(ds.asDatasetGraph());
+        Set<Endpoint> endpoints = new HashSet<>();
         
-        if ( ! dataService.getEndpoints(Operation.GSP_RW).isEmpty() || ! dataService.getEndpoints(Operation.Quads_RW).isEmpty() ) {
-            // ReadWrite available.
-            // Dispatch needs introspecting on the HTTP request.
-            dataService.addEndpoint(Operation.DatasetRequest_RW, "") ;
-        } else if ( ! dataService.getEndpoints(Operation.GSP_R).isEmpty() || ! dataService.getEndpoints(Operation.Quads_R).isEmpty() ) {
-            // Read-only available.
-            // Dispatch needs introspecting on the HTTP request.
-            dataService.addEndpoint(Operation.DatasetRequest_R, "") ;
-        }
-        
-        // XXX 
-        // This needs sorting out -- here, it is only on the whole server, not per dataset or even per service.
+        accEndpoint(endpoints, Operation.Query,    svc,  pServiceQueryEP);
+        accEndpoint(endpoints, Operation.Update,   svc,  pServiceUpdateEP);
+        accEndpoint(endpoints, Operation.Upload,   svc,  pServiceUploadEP);
+        accEndpoint(endpoints, Operation.GSP_R,    svc,  pServiceReadGraphStoreEP);
+        accEndpoint(endpoints, Operation.GSP_RW,   svc,  pServiceReadWriteGraphStoreEP);
+
+        endpoints.forEach(dataService::addEndpoint);
+
+        // TODO
+        // Setting timeout. This needs sorting out -- here, it is only on the whole server, not per dataset or even per service.
 //        // Extract timeout overriding configuration if present.
 //        if ( svc.hasProperty(FusekiVocab.pAllowTimeoutOverride) ) {
-//            sDesc.allowTimeoutOverride = svc.getProperty(FusekiVocab.pAllowTimeoutOverride).getObject().asLiteral().getBoolean() ;
+//            sDesc.allowTimeoutOverride = svc.getProperty(FusekiVocab.pAllowTimeoutOverride).getObject().asLiteral().getBoolean();
 //            if ( svc.hasProperty(FusekiVocab.pMaximumTimeoutOverride) ) {
-//                sDesc.maximumTimeoutOverride = (int)(svc.getProperty(FusekiVocab.pMaximumTimeoutOverride).getObject().asLiteral().getFloat() * 1000) ;
+//                sDesc.maximumTimeoutOverride = (int)(svc.getProperty(FusekiVocab.pMaximumTimeoutOverride).getObject().asLiteral().getFloat() * 1000);
 //            }
 //        }
-        return dataService ;
+        return dataService;
+    }
+    
+    private static boolean endpointsContains(Collection<Endpoint> endpoints, Operation operation) {
+        return endpoints.stream().anyMatch(ep->operation.equals(ep.getOperation()));
+    }
+
+    private static void accEndpoint(Collection<Endpoint> endpoints, Operation operation, Resource svc, Property property) {
+        String p = "<"+property.getURI()+">";
+        ResultSet rs = BuildLib.query("SELECT * { ?svc " + p + " ?ep}", svc.getModel(), "svc", svc);
+        for (; rs.hasNext(); ) {
+            QuerySolution soln = rs.next();
+            // No policy yet - set below if one is found.
+            AuthPolicy authPolicy = null;
+            RDFNode ep = soln.get("ep");
+            String epName = null;
+            if ( ep.isLiteral() )
+                epName = soln.getLiteral("ep").getLexicalForm();
+            else if ( ep.isResource() ) {
+                Resource r = (Resource)ep;
+                try {
+                    // Look for possible:
+                    // [ fuseki:name ""; fuseki:allowedUsers ( "" "" ) ]
+                    epName = r.getProperty(FusekiVocab.pServiceName).getString();
+                    List<RDFNode> x = GraphUtils.multiValue(r, FusekiVocab.pAllowedUsers);
+                    if ( x.size() > 1 )
+                        throw new FusekiConfigException("Multiple fuseki:"+FusekiVocab.pAllowedUsers.getLocalName()+" for "+r);
+                    if ( ! x.isEmpty() )
+                        authPolicy = allowedUsers(r);
+                } catch(JenaException | ClassCastException ex) {
+                    throw new FusekiConfigException("Failed to parse endpoint: "+r);
+                }
+            } else {
+                throw new FusekiConfigException("Unrecognized: "+ep);
+            }
+            
+            if ( StringUtils.isEmpty(epName) )
+                epName = null;
+            Endpoint endpoint = new Endpoint(operation, epName, authPolicy);
+            endpoints.add(endpoint);
+        }
     }
     
-    public static Dataset getDataset(Resource datasetDesc, DatasetDescriptionRegistry dsDescMap) {
+    private static void accEndpoint(Collection<Endpoint> endpoints, Operation operation) {
+        accEndpoint(endpoints, operation, null);
+    }
+
+    private static void accEndpoint(Collection<Endpoint> endpoints, Operation operation, String endpointName) {
+        accEndpoint(endpoints, operation, endpointName, null);
+    }
+
+    private static void accEndpoint(Collection<Endpoint> endpoints, Operation operation, String endpointName, AuthPolicy authPolicy) {
+        if ( StringUtils.isEmpty(endpointName) )
+            endpointName = null;
+        Endpoint endpoint = new Endpoint(operation, endpointName, authPolicy);
+        endpoints.add(endpoint);
+    }
+
+    public static Dataset getDataset(Resource datasetDesc, DatasetDescriptionMap dsDescMap) {
         // check if this one already built
         Dataset ds = dsDescMap.get(datasetDesc);
         if (ds == null) {
             // Check if the description is in the model.
             if ( !datasetDesc.hasProperty(RDF.type) )
-                throw new FusekiConfigException("No rdf:type for dataset " + FusekiBuildLib.nodeLabel(datasetDesc)) ;
+                throw new FusekiConfigException("No rdf:type for dataset " + BuildLib.nodeLabel(datasetDesc));
 
-            // Should have been done already. e.g. ActionDatasets.execPostContainer, 
+            // Should have been done already. e.g. ActionDatasets.execPostContainer,
             // Assemblerutils.readAssemblerFile < FusekiServer.parseConfigFile.
             //AssemblerUtils.addRegistered(datasetDesc.getModel());
-            ds = (Dataset)Assembler.general.open(datasetDesc) ;
+            ds = (Dataset)Assembler.general.open(datasetDesc);
         }
-        // Some kind of check that it is "the same" dataset.  
+        // Some kind of check that it is "the same" dataset.
         // It can be different if two descriptions in different files have the same URI.
         dsDescMap.register(datasetDesc, ds);
         return ds;
@@ -363,42 +520,42 @@ public class FusekiConfig {
     /** Read the system database */
     public static List<DataAccessPoint> readSystemDatabase(Dataset ds) {
         // Webapp only.
-        DatasetDescriptionRegistry dsDescMap = new DatasetDescriptionRegistry() ;
+        DatasetDescriptionMap dsDescMap = new DatasetDescriptionMap();
         String qs = StrUtils.strjoinNL
-            (FusekiConst.PREFIXES ,
+            (FusekiPrefixes.PREFIXES ,
              "SELECT * {" ,
              "  GRAPH ?g {",
-             "     ?s fu:name ?name ;" ,
+             "     ?s fu:name ?name;" ,
              "        fu:status ?status ." ,
              "  }",
              "}"
-             ) ;
-        
-        List<DataAccessPoint> refs = new ArrayList<>() ;
-        
-        ds.begin(ReadWrite.WRITE) ;
+             );
+
+        List<DataAccessPoint> refs = new ArrayList<>();
+
+        ds.begin(ReadWrite.WRITE);
         try {
-            ResultSet rs = FusekiBuildLib.query(qs, ds) ;
+            ResultSet rs = BuildLib.query(qs, ds);
 
-    //        ResultSetFormatter.out(rs); 
+    //        ResultSetFormatter.out(rs);
     //        ((ResultSetRewindable)rs).reset();
 
-            for ( ; rs.hasNext() ; ) {
-                QuerySolution row = rs.next() ;
-                Resource s = row.getResource("s") ;
-                Resource g = row.getResource("g") ;
-                Resource rStatus = row.getResource("status") ;
-                //String name = row.getLiteral("name").getLexicalForm() ;
-                DataServiceStatus status = DataServiceStatus.status(rStatus) ;
+            for (; rs.hasNext(); ) {
+                QuerySolution row = rs.next();
+                Resource s = row.getResource("s");
+                Resource g = row.getResource("g");
+                Resource rStatus = row.getResource("status");
+                //String name = row.getLiteral("name").getLexicalForm();
+                DataServiceStatus status = DataServiceStatus.status(rStatus);
 
-                Model m = ds.getNamedModel(g.getURI()) ;
+                Model m = ds.getNamedModel(g.getURI());
                 // Rebase the resource of the service description to the containing graph.
-                Resource svc = m.wrapAsResource(s.asNode()) ;
-                DataAccessPoint ref = buildDataAccessPoint(svc, dsDescMap) ;
-                refs.add(ref) ;
+                Resource svc = m.wrapAsResource(s.asNode());
+                DataAccessPoint ref = buildDataAccessPoint(svc, dsDescMap);
+                refs.add(ref);
             }
-            ds.commit(); 
-            return refs ;
-        } finally { ds.end() ; }
+            ds.commit();
+            return refs;
+        } finally { ds.end(); }
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConst.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiPrefixes.java
similarity index 95%
rename from jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConst.java
rename to jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiPrefixes.java
index 8935029..a4689dd 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConst.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiPrefixes.java
@@ -20,8 +20,8 @@ package org.apache.jena.fuseki.build;
 
 import org.apache.jena.atlas.lib.StrUtils;
 
-/** Internal constants */
-public class FusekiConst {
+/** Convenience set of prefixes */
+public class FusekiPrefixes {
 
     public static String PREFIXES = StrUtils.strjoinNL
     ("BASE            <http://example/base#>",
@@ -37,6 +37,6 @@ public class FusekiConst {
      "PREFIX apf:     <http://jena.apache.org/ARQ/property#>",
      "PREFIX afn:     <http://jena.apache.org/ARQ/function#>",
      "",
-     "") ;
+     "");
 
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionAsyncTask.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionAsyncTask.java
index 4310c6f..2e72ddb 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionAsyncTask.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionAsyncTask.java
@@ -18,39 +18,37 @@
 
 package org.apache.jena.fuseki.ctl;
 
-import org.apache.jena.atlas.json.JsonValue ;
-import org.apache.jena.atlas.lib.InternalErrorException ;
-import org.apache.jena.fuseki.async.AsyncPool ;
-import org.apache.jena.fuseki.async.AsyncTask ;
-import org.apache.jena.fuseki.servlets.HttpAction ;
-import org.apache.jena.fuseki.servlets.ServletOps ;
-
-/** Base helper class for creating async tasks on "items", based on POST  */ 
+import org.apache.jena.atlas.json.JsonValue;
+import org.apache.jena.atlas.lib.InternalErrorException;
+import org.apache.jena.fuseki.async.AsyncPool;
+import org.apache.jena.fuseki.async.AsyncTask;
+import org.apache.jena.fuseki.servlets.HttpAction;
+
+/** Base helper class for creating async tasks on "items", based on POST  */
 public abstract class ActionAsyncTask extends ActionItem
 {
-    public ActionAsyncTask() { super() ; }
-    
-    @Override
-    final
-    protected void execGet(HttpAction action) {
-        ServletOps.errorMethodNotAllowed(METHOD_GET);
+    private String name;
+
+    public ActionAsyncTask(String name) {
+        super();
+        this.name = name;
     }
 
     @Override
     final
-    protected JsonValue execGetItem(HttpAction action) { 
-        throw new InternalErrorException("GET for AsyncTask -- Should not be here!") ;
+    protected JsonValue execGetItem(HttpAction action) {
+        throw new InternalErrorException("GET for AsyncTask -- Should not be here!");
     }
 
     @Override
     final
     protected JsonValue execPostItem(HttpAction action) {
-        Runnable task = createRunnable(action) ;
-        AsyncTask aTask = Async.execASyncTask(action, AsyncPool.get(), "backup", task) ;
+        Runnable task = createRunnable(action);
+        AsyncTask aTask = Async.execASyncTask(action, AsyncPool.get(), name, task);
         Async.setLocationHeader(action, aTask);
-        return Async.asJson(aTask) ;
+        return Async.asJson(aTask);
     }
-    
-    protected abstract Runnable createRunnable(HttpAction action) ;
+
+    protected abstract Runnable createRunnable(HttpAction action);
 }
 
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 224c2f3..1d8c0e7 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
@@ -18,101 +18,95 @@
 
 package org.apache.jena.fuseki.ctl;
 
-import javax.servlet.http.HttpServletRequest ;
-import javax.servlet.http.HttpServletResponse ;
+import static org.apache.jena.riot.web.HttpNames.METHOD_DELETE;
+import static org.apache.jena.riot.web.HttpNames.METHOD_GET;
+import static org.apache.jena.riot.web.HttpNames.METHOD_POST;
 
-import org.apache.jena.atlas.json.JsonValue ;
-import org.apache.jena.fuseki.servlets.HttpAction ;
-import org.apache.jena.fuseki.servlets.ServletOps ;
-import org.apache.jena.web.HttpSC ;
+import org.apache.jena.atlas.json.JsonValue;
+import org.apache.jena.fuseki.servlets.ActionLib;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.ServletOps;
+import org.apache.jena.web.HttpSC;
 
-/** Base for actions that are container and also have actions on items */ 
+/** Base for actions that are container and also have actions on items */
 public abstract class ActionContainerItem extends ActionCtl {
-    
-    public ActionContainerItem() { super() ; }
 
-    // Redirect operations so they dispatch to perform(HttpAction)
-    @Override
-    final protected void doGet(HttpServletRequest request, HttpServletResponse response) {
-        doCommon(request, response);
-    }
+    protected ActionContainerItem() { super(); }
 
     @Override
-    final protected void doPost(HttpServletRequest request, HttpServletResponse response) {
-        doCommon(request, response);
-    }
-    
-    @Override
-    final protected void doHead(HttpServletRequest request, HttpServletResponse response) {
-        doCommon(request, response);
-    }
-    
-    @Override
-    final protected void doDelete(HttpServletRequest request, HttpServletResponse response) {
-        doCommon(request, response);
-    }
-    
-    @Override
     final
-    protected void perform(HttpAction action) {
-        String method = action.request.getMethod() ;
+    public void execute(HttpAction action) {
+        String method = action.request.getMethod();
         if ( method.equals(METHOD_GET) )
-            execGet(action) ;
+            performGet(action);
         else if ( method.equals(METHOD_POST) )
-            execPost(action) ;
+            performPost(action);
         else if ( method.equals(METHOD_DELETE) )
-            execDelete(action) ;
+            performDelete(action);
         else
-            ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ;
+            ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405);
+    }
+
+
+    @Override
+    public void execOptions(HttpAction action) {
+        ActionLib.doOptionsGetPostDeleteHead(action);
+        ServletOps.success(action);
     }
 
-    protected void execGet(HttpAction action) {
-        JsonValue v ;
+    final
+    // Container action if the name used to route to the servlet has more path.
+    protected boolean isContainerAction(HttpAction action) {
+        return (getItemName(action) == null );
+    }
+
+    protected void performGet(HttpAction action) {
+        JsonValue v;
         if ( isContainerAction(action)  )
-            v = execGetContainer(action) ;
+            v = execGetContainer(action);
         else
-            v = execGetItem(action) ;
-        
+            v = execGetItem(action);
+
         ServletOps.sendJsonReponse(action, v);
     }
-    
-    /** GET request on the container - respond with JSON, or null for plain 200 */  
-    protected abstract JsonValue execGetContainer(HttpAction action) ;
-    /** GET request on an item in the container - respond with JSON, or null for plain 200 */  
-    protected abstract JsonValue execGetItem(HttpAction action) ;
-
-    protected void execPost(HttpAction action) {
-        JsonValue v ;
+
+    /** GET request on the container - respond with JSON, or null for plain 200 */
+    protected abstract JsonValue execGetContainer(HttpAction action);
+    /** GET request on an item in the container - respond with JSON, or null for plain 200 */
+    protected abstract JsonValue execGetItem(HttpAction action);
+
+    protected void performPost(HttpAction action) {
+        JsonValue v;
         if ( isContainerAction(action) )
-            v = execPostContainer(action) ;
+            v = execPostContainer(action);
         else
-            v = execPostItem(action) ;
-        
+            v = execPostItem(action);
+
         ServletOps.sendJsonReponse(action, v);
     }
-    
-    /** POST request on the container - respond with JSON, or null for plain 200 */  
-    protected abstract JsonValue execPostContainer(HttpAction action) ;
-    /** POST request on an item in the container - respond with JSON, or null for plain 200 */  
-    protected abstract JsonValue execPostItem(HttpAction action) ;
 
-    
+    /** POST request on the container - respond with JSON, or null for plain 200 */
+    protected abstract JsonValue execPostContainer(HttpAction action);
+    /** POST request on an item in the container - respond with JSON, or null for plain 200 */
+    protected abstract JsonValue execPostItem(HttpAction action);
+
+
     /** DELETE request */
-    protected void execDelete(HttpAction action) {
+    protected void performDelete(HttpAction action) {
         if ( isContainerAction(action)  )
-            execDeleteContainer(action) ;
-        else 
-            execDeleteItem(action) ;
-        ServletOps.success(action) ;
+            execDeleteContainer(action);
+        else
+            execDeleteItem(action);
+        ServletOps.success(action);
     }
-    
+
     /** DELETE request on an item in the container */
     protected void execDeleteContainer(HttpAction action) {
-        ServletOps.errorMethodNotAllowed(METHOD_DELETE, "DELETE applied to a container") ;
+        ServletOps.errorMethodNotAllowed(METHOD_DELETE, "DELETE applied to a container");
     }
 
     /** DELETE request on an item in the container */
     protected void execDeleteItem(HttpAction action) {
-        ServletOps.errorMethodNotAllowed(METHOD_DELETE) ;
+        ServletOps.errorMethodNotAllowed(METHOD_DELETE);
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionCtl.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionCtl.java
index 9ed6293..c887352 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionCtl.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionCtl.java
@@ -18,68 +18,80 @@
 
 package org.apache.jena.fuseki.ctl;
 
-import org.apache.jena.fuseki.Fuseki ;
-import org.apache.jena.fuseki.server.DataAccessPoint ;
-import org.apache.jena.fuseki.servlets.ActionBase ;
-import org.apache.jena.fuseki.servlets.HttpAction ;
-import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.server.DataAccessPoint;
+import org.apache.jena.fuseki.servlets.ActionLifecycle;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.ServletProcessor;
+import org.apache.jena.sparql.core.DatasetGraph;
 
-/** Control/admin request lifecycle */
-public abstract class ActionCtl extends ActionBase {
+/**
+ * Base class for control actions. These are servlets and do not go through Fuseki
+ * dynamic dispatch. No statistics.
+ */
+public abstract class ActionCtl extends ServletProcessor implements ActionLifecycle {
+    protected ActionCtl() {
+        super(Fuseki.adminLog);
+    }
 
-    protected ActionCtl() { super(Fuseki.adminLog) ; }
-    
     @Override
-    final
-    protected void execCommonWorker(HttpAction action) {
-        DataAccessPoint dataAccessPoint ;
-        
-        String datasetUri = mapRequestToDatasetName(action) ;
-        if ( datasetUri != null ) {
-            dataAccessPoint = action.getDataAccessPointRegistry().get(datasetUri) ;
-            if ( dataAccessPoint == null ) {
-                ServletOps.errorNotFound("Not found: "+datasetUri) ;
-                return ;
-            }
-        }
-        else {
-            // This is a placeholder when creating new DatasetRefs
-            // and also if addressing a container, not a dataset
-            dataAccessPoint = null ;
-        }
-        
-        action.setControlRequest(dataAccessPoint, datasetUri) ;
-        action.setEndpoint(null) ;   // No operation or service name.
-        executeAction(action) ;
+    final public void process(HttpAction action) {
+        executeLifecycle(action);
     }
 
-    protected String mapRequestToDatasetName(HttpAction action) {
-        return extractItemName(action) ;
+    /**
+     * Simple execution lifecycle for a SPARQL Request. No statistics.
+     *
+     * @param action
+     */
+    protected void executeLifecycle(HttpAction action) {
+        validate(action);
+        execute(action);
     }
 
-    // Possible intercept point 
-    protected void executeAction(HttpAction action) {
-        executeLifecycle(action) ;
+    /** Get the item name - the part after the URI for the servlet (which is the container). */
+    public static String getItemName(HttpAction action) {
+        return action.request.getPathInfo();
     }
-    
-    // This is the service request lifecycle.
-    final
-    protected void executeLifecycle(HttpAction action) {
-        perform(action) ;
+
+    /**
+     * Get the item name - the part after the URI for the servlet (which is
+     * the container) - treated as a dataset name.
+     */
+    public static String getItemDatasetName(HttpAction action) {
+        String x = getItemName(action);
+        if ( x == null )
+            return null;
+        while ( x.startsWith("//") )
+            x = x.substring(1);
+        return DataAccessPoint.canonical(x);
     }
-    
-    final
-    protected boolean isContainerAction(HttpAction action) {
-        return (action.getDataAccessPoint() == null ) ;
+
+    /**
+     * Get the DataAccessPoint corresponding to the item name, or null.
+     * @see #getItemDatasetName
+     */
+    public static DataAccessPoint getItemDataAccessPoint(HttpAction action) {
+        String name = getItemDatasetName(action);
+        return getItemDataAccessPoint(action, name);
+    }
+
+    /**
+     * Get the DatasetGraph corresponding to the item name, or null.
+     * @see #getItemDatasetName
+     */
+    public static DatasetGraph getItemDataset(HttpAction action) {
+        DataAccessPoint dap = getItemDataAccessPoint(action);
+        if ( dap == null )
+            return null;
+        return dap.getDataService().getDataset();
     }
-    
-    protected abstract void perform(HttpAction action) ;
 
-//    /** Map request to uri in the registry.
-//     *  null means no mapping done (passthrough). 
-//     */
-//    protected String mapRequestToDataset(HttpAction action) 
-//    {
-//        return ActionLib.mapRequestToDataset(action.request.getRequestURI()) ;
-//    }
+    /**
+     * Get the DataAccessPoint corresponding to the item name, or null.
+     * @see #getItemDatasetName
+     */
+    public static DataAccessPoint getItemDataAccessPoint(HttpAction action, String name) {
+        return action.getDataAccessPointRegistry().get(name);
+    }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionDumpRequest.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionDumpRequest.java
index 88bbdc7..83ac976 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionDumpRequest.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionDumpRequest.java
@@ -50,7 +50,7 @@ public class ActionDumpRequest extends HttpServlet {
     public void doGet(HttpServletRequest req, HttpServletResponse resp) {
         doPrintInformation(req, resp);
     }
-    
+
     @Override
     public void doPost(HttpServletRequest req, HttpServletResponse resp) {
         doPrintInformation(req, resp);
@@ -106,7 +106,7 @@ public class ActionDumpRequest extends HttpServlet {
     }
 
     // ---- Library of things to report on.
-    
+
     static public void printRequest(PrintWriter pw, HttpServletRequest req) {
         // ----Standard environment
         pw.println("Method:                 " + req.getMethod());
@@ -128,9 +128,9 @@ public class ActionDumpRequest extends HttpServlet {
         pw.println("getRemoteHost:          " + req.getRemoteHost());
         pw.println("getRequestedSessionId:  " + req.getRequestedSessionId());
     }
-    
+
     // ---- Library of things to report on.
-    
+
     static void printBody(PrintWriter pw, HttpServletRequest req) throws IOException {
         // Destructive read of the request body.
         BufferedReader in = req.getReader();
@@ -150,7 +150,7 @@ public class ActionDumpRequest extends HttpServlet {
         if ( c == null )
             pw.println("getCookies:            <none>");
         else {
-            for ( int i = 0 ; i < c.length ; i++ ) {
+            for ( int i = 0; i < c.length; i++ ) {
                 pw.println();
                 pw.println("Cookie:        " + c[i].getName());
                 pw.println("    value:     " + c[i].getValue());
@@ -167,7 +167,7 @@ public class ActionDumpRequest extends HttpServlet {
     static void printHeaders(PrintWriter pw, HttpServletRequest req) {
         Enumeration<String> en = req.getHeaderNames();
 
-        for ( ; en.hasMoreElements() ; ) {
+        for (; en.hasMoreElements(); ) {
             String name = en.nextElement();
             String value = req.getHeader(name);
             pw.println("Head: " + name + " = " + value);
@@ -177,25 +177,24 @@ public class ActionDumpRequest extends HttpServlet {
     // Note that doing this on a form causes the forms content (body) to be read
     // and parsed as form variables.
     static void printParameters(PrintWriter pw, HttpServletRequest req) {
-        Enumeration<String> en = req.getParameterNames() ;
-        for ( ; en.hasMoreElements() ; )
-        {
-            String name = en.nextElement() ;
-            String value = req.getParameter(name) ;
-            pw.println("Param: "+name + " = " + value) ;
+        Enumeration<String> en = req.getParameterNames();
+        for (; en.hasMoreElements(); ) {
+            String name = en.nextElement();
+            String value = req.getParameter(name);
+            pw.println("Param: " + name + " = " + value);
         }
     }
-    
+
     static void printQueryString(PrintWriter pw, HttpServletRequest req) {
-        Multimap<String, String> map = FusekiNetLib.parseQueryString(req) ;
+        Multimap<String, String> map = FusekiNetLib.parseQueryString(req);
         for ( String name : map.keys() )
             for ( String value : map.get(name) )
-                pw.println("Param: "+name + " = " + value) ;
+                pw.println("Param: "+name + " = " + value);
     }
-    
+
     static void printLocales(PrintWriter pw, HttpServletRequest req) {
         Enumeration<Locale> en = req.getLocales();
-        for ( ; en.hasMoreElements() ; ) {
+        for (; en.hasMoreElements(); ) {
             String name = en.nextElement().toString();
             pw.println("Locale: " + name);
         }
@@ -204,7 +203,7 @@ public class ActionDumpRequest extends HttpServlet {
 
     /**
      * <code>printEnvironment</code>
-     * 
+     *
      * @return String that is the HTML of the System properties as name/value pairs. The
      *         values are with single quotes independent of whether or not the value has
      *         single quotes in it.
@@ -225,7 +224,7 @@ public class ActionDumpRequest extends HttpServlet {
             return null;
         }
     }
-    
+
     public String printServletContext() {
         try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw);) {
             ServletContext sc = getServletContext();
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 ea015e5..c2b4100 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
@@ -18,29 +18,29 @@
 
 package org.apache.jena.fuseki.ctl;
 
-import org.apache.jena.atlas.json.JsonValue ;
+import org.apache.jena.atlas.json.JsonValue;
 import org.apache.jena.fuseki.ctl.ActionContainerItem;
 import org.apache.jena.fuseki.servlets.HttpAction;
 import org.apache.jena.fuseki.servlets.ServletOps;
-import org.apache.jena.web.HttpSC ;
+import org.apache.jena.web.HttpSC;
 
-/** Action on items in a container, but not the container itself */ 
+/** Action on items in a container, but not the container itself */
 public abstract class ActionItem extends ActionContainerItem
 {
-    public ActionItem() { super() ; }
-    
+    public ActionItem() { super(); }
+
     @Override
     final
     protected JsonValue execGetContainer(HttpAction action) {
-        ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ;
-        return null ;
+        ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405);
+        return null;
     }
 
     @Override
     final
     protected JsonValue execPostContainer(HttpAction action) {
-        ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ;
-        return null ;
+        ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405);
+        return null;
     }
 }
 
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 cecf576..d488938 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,29 +17,32 @@
  */
 package org.apache.jena.fuseki.ctl;
 
-import java.io.IOException;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 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 {
 
-    @Override
-    public void init(ServletConfig config) throws ServletException {
-        super.init( config );
+    public ActionMetrics() { super(); }
 
+    @Override
+    public void execGet(HttpAction action) {
+        super.executeLifecycle(action);
     }
 
     @Override
-    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        super.doCommon(req, resp);
+    public void execOptions(HttpAction action) {
+        ActionLib.doOptionsGet(action);
+        ServletOps.success(action);
     }
 
     @Override
-    protected void perform(HttpAction action) {
+    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-core/src/main/java/org/apache/jena/fuseki/ctl/ActionPing.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionPing.java
index 2e43b22..8f3097f 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionPing.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionPing.java
@@ -18,55 +18,55 @@
 
 package org.apache.jena.fuseki.ctl;
 
-import static org.apache.jena.riot.WebContent.charsetUTF8 ;
-import static org.apache.jena.riot.WebContent.contentTypeTextPlain ;
+import static org.apache.jena.riot.WebContent.charsetUTF8;
+import static org.apache.jena.riot.WebContent.contentTypeTextPlain;
 
-import java.io.IOException ;
+import java.io.IOException;
 
-import javax.servlet.ServletOutputStream ;
-import javax.servlet.http.HttpServlet ;
-import javax.servlet.http.HttpServletRequest ;
-import javax.servlet.http.HttpServletResponse ;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
-import org.apache.jena.atlas.lib.DateTimeUtils ;
-import org.apache.jena.fuseki.Fuseki ;
-import org.apache.jena.fuseki.servlets.ServletOps ;
-import org.apache.jena.web.HttpSC ;
+import org.apache.jena.atlas.lib.DateTimeUtils;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.servlets.ServletOps;
+import org.apache.jena.web.HttpSC;
 
 /** The ping servlet provides a low cost, uncached endpoint that can be used
  * to determine if this component is running and responding.  For example,
- * a nagios check should use this endpoint.    
+ * a nagios check should use this endpoint.
  */
 public class ActionPing extends HttpServlet
 {
     // Ping is special.
     // To avoid excessive logging and id allocation for a "noise" operation,
     // this is a raw servlet.
-    public ActionPing() { super() ; } 
-    
+    public ActionPing() { super(); }
+
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
-        doCommon(req, resp); 
+        doCommon(req, resp);
     }
-    
+
     @Override
     protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
-        doCommon(req, resp); 
+        doCommon(req, resp);
     }
-    
+
 
     @Override
     protected void doHead(HttpServletRequest req, HttpServletResponse resp) {
-        doCommon(req, resp); 
+        doCommon(req, resp);
     }
 
     protected void doCommon(HttpServletRequest request, HttpServletResponse response) {
         try {
-            ServletOps.setNoCache(response) ; 
+            ServletOps.setNoCache(response);
             response.setContentType(contentTypeTextPlain);
-            response.setCharacterEncoding(charsetUTF8) ;
+            response.setCharacterEncoding(charsetUTF8);
             response.setStatus(HttpSC.OK_200);
-            ServletOutputStream out = response.getOutputStream() ;
+            ServletOutputStream out = response.getOutputStream();
             out.println(DateTimeUtils.nowAsXSDDateTimeString());
         } catch (IOException ex) {
             Fuseki.serverLog.warn("ping :: IOException :: "+ex.getMessage());
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionSleep.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionSleep.java
index 1857a05..2f2b3b9 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionSleep.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionSleep.java
@@ -18,75 +18,77 @@
 
 package org.apache.jena.fuseki.ctl;
 
-import static java.lang.String.format ;
+import static java.lang.String.format;
 
-import javax.servlet.http.HttpServletRequest ;
-import javax.servlet.http.HttpServletResponse ;
-
-import org.apache.jena.atlas.json.JsonValue ;
-import org.apache.jena.atlas.lib.Lib ;
-import org.apache.jena.fuseki.async.AsyncPool ;
-import org.apache.jena.fuseki.async.AsyncTask ;
-import org.apache.jena.fuseki.servlets.HttpAction ;
-import org.apache.jena.fuseki.servlets.ServletOps ;
-import org.slf4j.Logger ;
+import org.apache.jena.atlas.json.JsonValue;
+import org.apache.jena.atlas.lib.Lib;
+import org.apache.jena.fuseki.async.AsyncPool;
+import org.apache.jena.fuseki.async.AsyncTask;
+import org.apache.jena.fuseki.servlets.ActionLib;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.ServletOps;
+import org.slf4j.Logger;
 
 /** A task that kicks off a asynchronous operation that simply waits and exits.  For testing. */
 public class ActionSleep extends ActionCtl /* Not ActionAsyncTask - that is a container-item based. */
 {
-    public ActionSleep() { super() ; }
-    
-    // And only POST
+    public ActionSleep() { super(); }
+
+    @Override
+    public void execOptions(HttpAction action) {
+        ActionLib.doOptionsPost(action);
+        ServletOps.success(action);
+    }
+
     @Override
-    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
-        doCommon(request, response);
+    public void execPost(HttpAction action) {
+        super.executeLifecycle(action);
     }
 
     @Override
-    protected void perform(HttpAction action) {
-        Runnable task = createRunnable(action) ;
-        AsyncTask aTask = Async.execASyncTask(action, AsyncPool.get(), "sleep", task) ;
-        JsonValue v = Async.asJson(aTask) ;
+    public void validate(HttpAction action) {}
+
+    @Override
+    public void execute(HttpAction action) {
+        Runnable task = createRunnable(action);
+        AsyncTask aTask = Async.execASyncTask(action, AsyncPool.get(), "sleep", task);
+        JsonValue v = Async.asJson(aTask);
         Async.setLocationHeader(action, aTask);
         ServletOps.sendJsonReponse(action, v);
     }
 
     protected Runnable createRunnable(HttpAction action) {
-        String name = action.getDatasetName() ;
-        if ( name == null )
-            name = "''" ;
-        
-        String interval = action.request.getParameter("interval") ;
-        int sleepMilli = 5000 ;
+        String interval = action.request.getParameter("interval");
+        int sleepMilli = 5000;
         if ( interval != null )
             try {
-                sleepMilli = Integer.parseInt(interval) ;
+                sleepMilli = Integer.parseInt(interval);
             } catch (NumberFormatException ex) {
-                action.log.error(format("[%d] NumberFormatException: %s", action.id, interval)) ; 
+                action.log.error(format("[%d] NumberFormatException: %s", action.id, interval));
             }
-        action.log.info(format("[%d] Sleep %s %d ms", action.id, name, sleepMilli)) ;
-        return new SleepTask(action, sleepMilli) ;
+        action.log.info(format("[%d] Sleep %d ms", action.id, sleepMilli));
+        return new SleepTask(action, sleepMilli);
     }
 
     static class SleepTask implements Runnable {
-        private final Logger log ;
-        private final long actionId ;
-        private final int sleepMilli ;
-        
+        private final Logger log;
+        private final long actionId;
+        private final int sleepMilli;
+
         public SleepTask(HttpAction action, int sleepMilli ) {
-            this.log = action.log ;
-            this.actionId = action.id ;
-            this.sleepMilli = sleepMilli ;
+            this.log = action.log;
+            this.actionId = action.id;
+            this.sleepMilli = sleepMilli;
         }
 
         @Override
         public void run() {
             try {
-                log.info(format("[%d] >> Sleep start", actionId)) ;
-                Lib.sleep(sleepMilli) ;
-                log.info(format("[%d] << Sleep finish", actionId)) ;
+                log.info(format("[%d] >> Sleep start", actionId));
+                Lib.sleep(sleepMilli);
+                log.info(format("[%d] << Sleep finish", actionId));
             } catch (Exception ex) {
-                log.info(format("[%d] **** Exception", actionId), ex) ;
+                log.info(format("[%d] **** Exception", actionId), ex);
             }
         }
     }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStats.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStats.java
index b6f7430..f3f8151 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStats.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStats.java
@@ -18,172 +18,189 @@
 
 package org.apache.jena.fuseki.ctl;
 
-import static java.lang.String.format ;
-import static org.apache.jena.riot.WebContent.charsetUTF8 ;
-import static org.apache.jena.riot.WebContent.contentTypeTextPlain ;
+import static java.lang.String.format;
+import static org.apache.jena.riot.WebContent.charsetUTF8;
+import static org.apache.jena.riot.WebContent.contentTypeTextPlain;
 
-import java.io.IOException ;
-import java.util.Iterator ;
-import java.util.List ;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
 
-import javax.servlet.ServletOutputStream ;
-import javax.servlet.http.HttpServletResponse ;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
 
-import org.apache.jena.atlas.json.JsonBuilder ;
-import org.apache.jena.atlas.json.JsonObject ;
-import org.apache.jena.atlas.json.JsonValue ;
-import org.apache.jena.fuseki.server.* ;
-import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jena.atlas.json.JsonBuilder;
+import org.apache.jena.atlas.json.JsonObject;
+import org.apache.jena.atlas.json.JsonValue;
+import org.apache.jena.fuseki.server.*;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.ServletOps;
 
 public class ActionStats extends ActionContainerItem
 {
-    public ActionStats() { super() ; } 
+    // For endpoint with "" as name.
+    private static String emptyNameKeyPrefix = "_"; 
+    
+    public ActionStats() { super(); }
+
+    @Override
+    public void validate(HttpAction action) {}
 
     // This does not consult the system database for dormant etc.
-    protected JsonValue execCommonContainer(HttpAction action) {            
-        action.log.info(format("[%d] GET stats all", action.id)) ;
-        return generateStats(action.getDataAccessPointRegistry()) ;
+    protected JsonValue execCommonContainer(HttpAction action) {
+        action.log.info(format("[%d] GET stats all", action.id));
+        return generateStats(action.getDataAccessPointRegistry());
     }
 
     public static JsonObject generateStats(DataAccessPointRegistry registry) {
-        JsonBuilder builder = new JsonBuilder() ;
-        builder.startObject("top") ;
-        builder.key(ServerConst.datasets) ;
-        builder.startObject("datasets") ;
+        JsonBuilder builder = new JsonBuilder();
+        builder.startObject("top");
+        builder.key(ServerConst.datasets);
+        builder.startObject("datasets");
         registry.forEach((name, access)->statsDataset(builder, access));
-        builder.finishObject("datasets") ;
-        builder.finishObject("top") ;
-        return builder.build().getAsObject() ;
+        builder.finishObject("datasets");
+        builder.finishObject("top");
+        return builder.build().getAsObject();
     }
-    
+
     protected JsonValue execCommonItem(HttpAction action) {
-        action.log.info(format("[%d] GET stats dataset %s", action.id, action.getDatasetName())) ;
-        
-        JsonBuilder builder = new JsonBuilder() ;
-        String datasetPath = DataAccessPoint.canonical(action.getDatasetName()) ;
-        builder.startObject("TOP") ;
-        
-        builder.key(ServerConst.datasets) ;
-        builder.startObject("datasets") ;
-        statsDataset(builder, datasetPath, action.getDataAccessPointRegistry()) ;
-        builder.finishObject("datasets") ;
-        
-        builder.finishObject("TOP") ;
-        return builder.build() ;
+        String datasetPath = getItemDatasetName(action);
+        action.log.info(format("[%d] GET stats dataset %s", action.id, datasetPath));
+
+        JsonBuilder builder = new JsonBuilder();
+        DataAccessPoint dap = getItemDataAccessPoint(action, datasetPath);
+        if ( dap == null )
+            ServletOps.errorNotFound(datasetPath);
+        builder.startObject("TOP");
+
+        builder.key(ServerConst.datasets);
+        builder.startObject("datasets");
+        statsDataset(builder, datasetPath, action.getDataAccessPointRegistry());
+        builder.finishObject("datasets");
+
+        builder.finishObject("TOP");
+        return builder.build();
     }
-    
+
     public static JsonObject generateStats(DataAccessPoint access) {
-        JsonBuilder builder = new JsonBuilder() ;
-        statsDataset(builder, access) ;
-        return builder.build().getAsObject() ;
+        JsonBuilder builder = new JsonBuilder();
+        statsDataset(builder, access);
+        return builder.build().getAsObject();
     }
-    
+
     private void statsDataset(JsonBuilder builder, String name, DataAccessPointRegistry registry) {
-        DataAccessPoint access = registry.get(name) ;
+        DataAccessPoint access = registry.get(name);
         statsDataset(builder, access);
     }
-    
+
     private static void statsDataset(JsonBuilder builder, DataAccessPoint access) {
         // Object started
-        builder.key(access.getName()) ;
-        DataService dSrv = access.getDataService() ;
-        builder.startObject("counters") ;
-        
-        builder.key(CounterName.Requests.getName()).value(dSrv.getCounters().value(CounterName.Requests)) ;
-        builder.key(CounterName.RequestsGood.getName()).value(dSrv.getCounters().value(CounterName.RequestsGood)) ;
-        builder.key(CounterName.RequestsBad.getName()).value(dSrv.getCounters().value(CounterName.RequestsBad)) ;
-        
-        builder.key(ServerConst.endpoints).startObject("endpoints") ;
-        
+        builder.key(access.getName());
+        DataService dSrv = access.getDataService();
+        builder.startObject("counters");
+
+        builder.key(CounterName.Requests.getName()).value(dSrv.getCounters().value(CounterName.Requests));
+        builder.key(CounterName.RequestsGood.getName()).value(dSrv.getCounters().value(CounterName.RequestsGood));
+        builder.key(CounterName.RequestsBad.getName()).value(dSrv.getCounters().value(CounterName.RequestsBad));
+
+        builder.key(ServerConst.endpoints).startObject("endpoints");
+        int unique = 0;
         for ( Operation operName : dSrv.getOperations() ) {
-            List<Endpoint> endpoints = access.getDataService().getEndpoints(operName) ;
-            
+            List<Endpoint> endpoints = access.getDataService().getEndpoints(operName);
+
             for ( Endpoint endpoint : endpoints ) {
+                String k = endpoint.getName();
+                if ( StringUtils.isEmpty(k) )
+                    k = emptyNameKeyPrefix+(++unique);
                 // Endpoint names are unique for a given service.
-                builder.key(endpoint.getName()) ;
-                builder.startObject() ;
-                
+                builder.key(k);
+                builder.startObject();
+
                 operationCounters(builder, endpoint);
-                builder.key(ServerConst.operation).value(operName.getName()) ;
+                builder.key(ServerConst.operation).value(operName.getName());
                 builder.key(ServerConst.description).value(operName.getDescription());
-                
-                builder.finishObject() ;
+
+                builder.finishObject();
             }
         }
-        builder.finishObject("endpoints") ;
-        builder.finishObject("counters") ;
+        builder.finishObject("endpoints");
+        builder.finishObject("counters");
     }
 
     private static void operationCounters(JsonBuilder builder, Endpoint operation) {
         for (CounterName cn : operation.getCounters().counters()) {
-            Counter c = operation.getCounters().get(cn) ;
-            builder.key(cn.getName()).value(c.value()) ;
+            Counter c = operation.getCounters().get(cn);
+            builder.key(cn.getName()).value(c.value());
         }
     }
 
-    private void statsTxt(HttpServletResponse resp, DataAccessPointRegistry registry) throws IOException
-    {
-        ServletOutputStream out = resp.getOutputStream() ;
+    private void statsTxt(HttpServletResponse resp, DataAccessPointRegistry registry) throws IOException {
+        ServletOutputStream out = resp.getOutputStream();
         resp.setContentType(contentTypeTextPlain);
-        resp.setCharacterEncoding(charsetUTF8) ;
-
-        Iterator<String> iter = registry.keys().iterator() ;
-        while(iter.hasNext())
-        {
-            String ds = iter.next() ;
-            DataAccessPoint desc = registry.get(ds) ;
-            statsTxt(out, desc) ;
+        resp.setCharacterEncoding(charsetUTF8);
+
+        Iterator<String> iter = registry.keys().iterator();
+        while (iter.hasNext()) {
+            String ds = iter.next();
+            DataAccessPoint desc = registry.get(ds);
+            statsTxt(out, desc);
             if ( iter.hasNext() )
-                out.println() ;
+                out.println();
         }
-        out.flush() ;
+        out.flush();
     }
-    
-    private void statsTxt(ServletOutputStream out, DataAccessPoint desc) throws IOException
-    {
-        DataService dSrv = desc.getDataService() ;
-        out.println("Dataset: "+desc.getName()) ;
-        out.println("    Requests      = "+dSrv.getCounters().value(CounterName.Requests)) ;
-        out.println("    Good          = "+dSrv.getCounters().value(CounterName.RequestsGood)) ;
-        out.println("    Bad           = "+dSrv.getCounters().value(CounterName.RequestsBad)) ;
-
-        out.println("  SPARQL Query:") ;
-        out.println("    Request       = "+counter(dSrv, Operation.Query, CounterName.Requests)) ;
-        out.println("    Good          = "+counter(dSrv, Operation.Query, CounterName.RequestsGood)) ;
-        out.println("    Bad requests  = "+counter(dSrv, Operation.Query, CounterName.RequestsBad)) ;
-        out.println("    Timeouts      = "+counter(dSrv, Operation.Query, CounterName.QueryTimeouts)) ;
-        out.println("    Bad exec      = "+counter(dSrv, Operation.Query, CounterName.QueryExecErrors)) ;
-        out.println("    IO Errors     = "+counter(dSrv, Operation.Query, CounterName.QueryIOErrors)) ;
-
-        out.println("  SPARQL Update:") ;
-        out.println("    Request       = "+counter(dSrv, Operation.Update, CounterName.Requests)) ;
-        out.println("    Good          = "+counter(dSrv, Operation.Update, CounterName.RequestsGood)) ;
-        out.println("    Bad requests  = "+counter(dSrv, Operation.Update, CounterName.RequestsBad)) ;
-        out.println("    Bad exec      = "+counter(dSrv, Operation.Update, CounterName.UpdateExecErrors)) ;
-        
-        out.println("  Upload:") ;
-        out.println("    Requests      = "+counter(dSrv, Operation.Upload, CounterName.Requests)) ;
-        out.println("    Good          = "+counter(dSrv, Operation.Upload, CounterName.RequestsGood)) ;
-        out.println("    Bad           = "+counter(dSrv, Operation.Upload, CounterName.RequestsBad)) ;
-        
-        out.println("  SPARQL Graph Store Protocol:") ;
-        out.println("    GETs          = "+gspValue(dSrv, CounterName.HTTPget)      + " (good="+gspValue(dSrv, CounterName.HTTPgetGood)+"/bad="+gspValue(dSrv, CounterName.HTTPgetBad)+")") ;
-        out.println("    PUTs          = "+gspValue(dSrv, CounterName.HTTPput)      + " (good="+gspValue(dSrv, CounterName.HTTPputGood)+"/bad="+gspValue(dSrv, CounterName.HTTPputBad)+")") ;
-        out.println("    POSTs         = "+gspValue(dSrv, CounterName.HTTPpost)     + " (good="+gspValue(dSrv, CounterName.HTTPpostGood)+"/bad="+gspValue(dSrv, CounterName.HTTPpostBad)+")") ;
-        out.println("    PATCHs        = "+gspValue(dSrv, CounterName.HTTPpatch)    + " (good="+gspValue(dSrv, CounterName.HTTPpatchGood)+"/bad="+gspValue(dSrv, CounterName.HTTPpatchBad)+")") ;
-        out.println("    DELETEs       = "+gspValue(dSrv, CounterName.HTTPdelete)   + " (good="+gspValue(dSrv, CounterName.HTTPdeleteGood)+"/bad="+gspValue(dSrv, CounterName.HTTPdeleteBad)+")") ;
-        out.println("    HEADs         = "+gspValue(dSrv, CounterName.HTTPhead)     + " (good="+gspValue(dSrv, CounterName.HTTPheadGood)+"/bad="+gspValue(dSrv, CounterName.HTTPheadBad)+")") ;
+
+    private void statsTxt(ServletOutputStream out, DataAccessPoint desc) throws IOException {
+        DataService dSrv = desc.getDataService();
+        out.println("Dataset: " + desc.getName());
+        out.println("    Requests      = " + dSrv.getCounters().value(CounterName.Requests));
+        out.println("    Good          = " + dSrv.getCounters().value(CounterName.RequestsGood));
+        out.println("    Bad           = " + dSrv.getCounters().value(CounterName.RequestsBad));
+
+        out.println("  SPARQL Query:");
+        out.println("    Request       = " + counter(dSrv, Operation.Query, CounterName.Requests));
+        out.println("    Good          = " + counter(dSrv, Operation.Query, CounterName.RequestsGood));
+        out.println("    Bad requests  = " + counter(dSrv, Operation.Query, CounterName.RequestsBad));
+        out.println("    Timeouts      = " + counter(dSrv, Operation.Query, CounterName.QueryTimeouts));
+        out.println("    Bad exec      = " + counter(dSrv, Operation.Query, CounterName.QueryExecErrors));
+        out.println("    IO Errors     = " + counter(dSrv, Operation.Query, CounterName.QueryIOErrors));
+
+        out.println("  SPARQL Update:");
+        out.println("    Request       = " + counter(dSrv, Operation.Update, CounterName.Requests));
+        out.println("    Good          = " + counter(dSrv, Operation.Update, CounterName.RequestsGood));
+        out.println("    Bad requests  = " + counter(dSrv, Operation.Update, CounterName.RequestsBad));
+        out.println("    Bad exec      = " + counter(dSrv, Operation.Update, CounterName.UpdateExecErrors));
+
+        out.println("  Upload:");
+        out.println("    Requests      = " + counter(dSrv, Operation.Upload, CounterName.Requests));
+        out.println("    Good          = " + counter(dSrv, Operation.Upload, CounterName.RequestsGood));
+        out.println("    Bad           = " + counter(dSrv, Operation.Upload, CounterName.RequestsBad));
+
+        out.println("  SPARQL Graph Store Protocol:");
+        out.println("    GETs          = " + gspValue(dSrv, CounterName.HTTPget) + " (good=" + gspValue(dSrv, CounterName.HTTPgetGood)
+                    + "/bad=" + gspValue(dSrv, CounterName.HTTPgetBad) + ")");
+        out.println("    PUTs          = " + gspValue(dSrv, CounterName.HTTPput) + " (good=" + gspValue(dSrv, CounterName.HTTPputGood)
+                    + "/bad=" + gspValue(dSrv, CounterName.HTTPputBad) + ")");
+        out.println("    POSTs         = " + gspValue(dSrv, CounterName.HTTPpost) + " (good=" + gspValue(dSrv, CounterName.HTTPpostGood)
+                    + "/bad=" + gspValue(dSrv, CounterName.HTTPpostBad) + ")");
+        out.println("    PATCHs        = " + gspValue(dSrv, CounterName.HTTPpatch) + " (good=" + gspValue(dSrv, CounterName.HTTPpatchGood)
+                    + "/bad=" + gspValue(dSrv, CounterName.HTTPpatchBad) + ")");
+        out.println("    DELETEs       = " + gspValue(dSrv, CounterName.HTTPdelete) + " (good=" + gspValue(dSrv, CounterName.HTTPdeleteGood)
+                    + "/bad=" + gspValue(dSrv, CounterName.HTTPdeleteBad) + ")");
+        out.println("    HEADs         = " + gspValue(dSrv, CounterName.HTTPhead) + " (good=" + gspValue(dSrv, CounterName.HTTPheadGood)
+                    + "/bad=" + gspValue(dSrv, CounterName.HTTPheadBad) + ")");
     }
-    
+
     private long counter(DataService dSrv, Operation operation, CounterName cName) {
-        return 0 ;
+        return 0;
     }
-    
+
     private long gspValue(DataService dSrv, CounterName cn) {
         return  counter(dSrv, Operation.GSP_RW, cn) +
-                counter(dSrv, Operation.GSP_R, cn) ;
+                counter(dSrv, Operation.GSP_R, cn);
     }
-    
+
     @Override
     protected JsonValue execPostContainer(HttpAction action) {
         return execCommonContainer(action);
@@ -198,11 +215,11 @@ public class ActionStats extends ActionContainerItem
     protected JsonValue execGetContainer(HttpAction action) {
         return execCommonContainer(action);
     }
-    
+
     @Override
     protected JsonValue execGetItem(HttpAction action) {
         return execCommonItem(action);
-    }    
+    }
 }
 
 
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 23f786a..df60bf9 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
@@ -17,109 +17,106 @@
  */
 
 package org.apache.jena.fuseki.ctl;
-import static java.lang.String.format ;
-
-import javax.servlet.http.HttpServletRequest ;
-import javax.servlet.http.HttpServletResponse ;
-
-import org.apache.jena.atlas.json.JsonBuilder ;
-import org.apache.jena.atlas.json.JsonValue ;
-import org.apache.jena.fuseki.Fuseki ;
-import org.apache.jena.fuseki.async.AsyncPool ;
-import org.apache.jena.fuseki.async.AsyncTask ;
-import org.apache.jena.fuseki.servlets.ActionBase ;
-import org.apache.jena.fuseki.servlets.HttpAction ;
-import org.apache.jena.fuseki.servlets.ServletOps ;
-import org.apache.jena.web.HttpSC ;
-
-public class ActionTasks extends ActionBase //ActionContainerItem
+import static java.lang.String.format;
+import static org.apache.jena.riot.web.HttpNames.METHOD_GET;
+import static org.apache.jena.riot.web.HttpNames.METHOD_POST;
+
+import org.apache.jena.atlas.json.JsonBuilder;
+import org.apache.jena.atlas.json.JsonValue;
+import org.apache.jena.fuseki.async.AsyncPool;
+import org.apache.jena.fuseki.async.AsyncTask;
+import org.apache.jena.fuseki.servlets.ActionLib;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.ServletOps;
+import org.apache.jena.web.HttpSC;
+
+public class ActionTasks extends ActionCtl
 {
-    private static AsyncPool[] pools = { AsyncPool.get() } ; 
-    
-    public ActionTasks() { super(Fuseki.serverLog) ; }
-    
+    private static AsyncPool[] pools = { AsyncPool.get() };
+
+    public ActionTasks() { super(); }
+
     @Override
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
-        doCommon(request, response);
+    public void execOptions(HttpAction action) {
+        ActionLib.doOptionsGetPost(action);
+        ServletOps.success(action);
     }
 
+    private static String prefix = "/";
+
     @Override
-    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
-        doCommon(request, response);
-    }
+    public void validate(HttpAction action) { }
 
-    private static String prefix = "/" ;
-    
     @Override
-    protected void execCommonWorker(HttpAction action) {
-        String name = extractItemName(action) ;
+    public void execute(HttpAction action) {
+        String name = ActionCtl.getItemName(action);
         if ( name != null ) {
             if ( name.startsWith(prefix))
-                name = name.substring(prefix.length()) ; 
+                name = name.substring(prefix.length());
             else
-                log.warn("Unexpected task name : "+name) ;
+                action.log.warn("Unexpected task name : "+name);
         }
-        
-        String method = action.request.getMethod() ;
+
+        String method = action.request.getMethod();
         if ( method.equals(METHOD_GET) )
-            execGet(action, name) ;
+            execGet(action, name);
         else if ( method.equals(METHOD_POST) )
-            execPost(action, name) ;
+            execPost(action, name);
         else
-            ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ;
+            ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405);
     }
 
     private void execGet(HttpAction action, String name) {
         if ( name == null )
-            log.info(format("[%d] Tasks", action.id));
+            action.log.info(format("[%d] Tasks", action.id));
         else
-            log.info(format("[%d] Task %s", action.id, name));
+            action.log.info(format("[%d] Task %s", action.id, name));
+
+        JsonValue responseBody = null;
 
-        JsonValue responseBody = null ;
-        
         if ( name == null ) {
-            JsonBuilder builder = new JsonBuilder() ;
-            builder.startArray() ;
-            
+            JsonBuilder builder = new JsonBuilder();
+            builder.startArray();
+
             for ( AsyncPool pool : pools ) {
                 for ( AsyncTask aTask : pool.tasks() ) {
-                    //builder.value(aTask.getTaskId()) ;
-                    descOneTask(builder, aTask) ;
+                    //builder.value(aTask.getTaskId());
+                    descOneTask(builder, aTask);
                 }
             }
-            builder.finishArray() ;
-            responseBody = builder.build(); 
+            builder.finishArray();
+            responseBody = builder.build();
         } else {
             for ( AsyncPool pool : pools ) {
                 // Assumes first is only.
-                AsyncTask aTask = pool.getTask(name) ;
+                AsyncTask aTask = pool.getTask(name);
                 if ( aTask != null ) {
-                    JsonBuilder builder = new JsonBuilder() ;
+                    JsonBuilder builder = new JsonBuilder();
                     descOneTask(builder, aTask);
-                    responseBody = builder.build() ;
+                    responseBody = builder.build();
                 }
             }
         }
-        
+
         if ( responseBody == null )
-            ServletOps.errorNotFound("Task '"+name+"' not found") ;
-        ServletOps.setNoCache(action) ; 
-        ServletOps.sendJsonReponse(action, responseBody); 
+            ServletOps.errorNotFound("Task '"+name+"' not found");
+        ServletOps.setNoCache(action);
+        ServletOps.sendJsonReponse(action, responseBody);
     }
 
     private void execPost(HttpAction action, String name) {
-        
+
     }
-    
+
     private static void descOneTask(JsonBuilder builder, AsyncTask aTask) {
-        builder.startObject("SingleTask") ;
-        builder.key(JsonConstCtl.task).value(aTask.displayName()) ;
-        builder.key(JsonConstCtl.taskId).value(aTask.getTaskId()) ;
+        builder.startObject("SingleTask");
+        builder.key(JsonConstCtl.task).value(aTask.displayName());
+        builder.key(JsonConstCtl.taskId).value(aTask.getTaskId());
         if ( aTask.getStartPoint() != null )
-            builder.key(JsonConstCtl.started).value(aTask.getStartPoint()) ;
+            builder.key(JsonConstCtl.started).value(aTask.getStartPoint());
         if ( aTask.getFinishPoint() != null )
-            builder.key(JsonConstCtl.finished).value(aTask.getFinishPoint()) ;
-        builder.finishObject("SingleTask") ;
+            builder.key(JsonConstCtl.finished).value(aTask.getFinishPoint());
+        builder.finishObject("SingleTask");
     }
 }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/Async.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/Async.java
index b3696c3..63262fe 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/Async.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/Async.java
@@ -18,44 +18,44 @@
 
 package org.apache.jena.fuseki.ctl;
 
-import org.apache.http.HttpHeaders ;
-import org.apache.jena.atlas.json.JsonBuilder ;
-import org.apache.jena.atlas.json.JsonValue ;
-import org.apache.jena.fuseki.async.AsyncPool ;
-import org.apache.jena.fuseki.async.AsyncTask ;
-import org.apache.jena.fuseki.server.DataService ;
-import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.http.HttpHeaders;
+import org.apache.jena.atlas.json.JsonBuilder;
+import org.apache.jena.atlas.json.JsonValue;
+import org.apache.jena.fuseki.async.AsyncPool;
+import org.apache.jena.fuseki.async.AsyncTask;
+import org.apache.jena.fuseki.server.DataService;
+import org.apache.jena.fuseki.servlets.HttpAction;
 
 public class Async
 {
     public static AsyncTask asyncTask(AsyncPool asyncPool, String displayName, DataService dataService, Runnable task, long requestId) {
-        AsyncTask asyncTask = asyncPool.submit(task, displayName, dataService, requestId) ;
-        return asyncTask ;
+        AsyncTask asyncTask = asyncPool.submit(task, displayName, dataService, requestId);
+        return asyncTask;
     }
-    
+
     public static JsonValue asJson(AsyncTask asyncTask) {
-        JsonBuilder builder = new JsonBuilder() ;
-        builder.startObject("outer") ;
-        builder.key(JsonConstCtl.taskId).value(asyncTask.getTaskId()) ;
+        JsonBuilder builder = new JsonBuilder();
+        builder.startObject("outer");
+        builder.key(JsonConstCtl.taskId).value(asyncTask.getTaskId());
         if ( asyncTask.getOriginatingRequestId() > 0 )
-            builder.key(JsonConstCtl.taskRequestId).value(asyncTask.getOriginatingRequestId()) ;
-        builder.finishObject("outer") ;
-        return builder.build() ;
+            builder.key(JsonConstCtl.taskRequestId).value(asyncTask.getOriginatingRequestId());
+        builder.finishObject("outer");
+        return builder.build();
     }
-    
+
     public static void setLocationHeader(HttpAction action, AsyncTask asyncTask) {
-        String x = action.getRequest().getRequestURI() ;
+        String x = action.getRequest().getRequestURI();
         if ( ! x.endsWith("/") )
-            x += "/" ;
-        x += asyncTask.getTaskId() ;
-        //String x = "/$/tasks/"+asyncTask.getTaskId() ;
-        action.getResponse().setHeader(HttpHeaders.LOCATION, x) ;
+            x += "/";
+        x += asyncTask.getTaskId();
+        //String x = "/$/tasks/"+asyncTask.getTaskId();
+        action.getResponse().setHeader(HttpHeaders.LOCATION, x);
     }
 
     public static AsyncTask execASyncTask(HttpAction action, AsyncPool asyncPool, String displayName, Runnable runnable) {
-        AsyncTask atask = Async.asyncTask(asyncPool, displayName, action.getDataService(), runnable, action.id) ;
-        Async.setLocationHeader(action, atask); 
-        return atask ;
+        AsyncTask atask = Async.asyncTask(asyncPool, displayName, action.getDataService(), runnable, action.id);
+        Async.setLocationHeader(action, atask);
+        return atask;
     }
 }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonConstCtl.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonConstCtl.java
index 7007098..6f439d6 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonConstCtl.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonConstCtl.java
@@ -20,10 +20,10 @@ package org.apache.jena.fuseki.ctl;
 
 public class JsonConstCtl {
 
-    public static final String taskId           = "taskId" ;
-    public static final String taskRequestId    = "requestId" ;
-    public static final String task             = "task" ;
-    public static final String finished         = "finished" ;
-    public static final String started          = "started" ;
+    public static final String taskId           = "taskId";
+    public static final String taskRequestId    = "requestId";
+    public static final String task             = "task";
+    public static final String finished         = "finished";
+    public static final String started          = "started";
 
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonDescription.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonDescription.java
index e74b0a4..b76cb76 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonDescription.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonDescription.java
@@ -18,56 +18,56 @@
 
 package org.apache.jena.fuseki.ctl;
 
-import java.util.List ;
+import java.util.List;
 
-import org.apache.jena.atlas.json.JsonBuilder ;
-import org.apache.jena.fuseki.server.DataAccessPoint ;
-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.atlas.json.JsonBuilder;
+import org.apache.jena.fuseki.server.DataAccessPoint;
+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.server.ServerConst;
 
 /** Create a description of a service */
 public class JsonDescription {
-    
+
     public static void arrayDatasets(JsonBuilder builder, DataAccessPointRegistry registry) {
-        builder.startArray() ;
+        builder.startArray();
         for ( String ds : registry.keys() ) {
-            DataAccessPoint access = registry.get(ds) ;
-            JsonDescription.describe(builder, access) ;
+            DataAccessPoint access = registry.get(ds);
+            JsonDescription.describe(builder, access);
         }
-        builder.finishArray() ;
+        builder.finishArray();
     }
-    
+
     public static void describe(JsonBuilder builder, DataAccessPoint access) {
-        builder.startObject() ;
-        builder.key(ServerConst.dsName).value(access.getName()) ;
-        
-        builder.key(ServerConst.dsState).value(access.getDataService().isAcceptingRequests()) ;
-        
-        builder.key(ServerConst.dsService) ;
-        builder.startArray() ;
-        
+        builder.startObject();
+        builder.key(ServerConst.dsName).value(access.getName());
+
+        builder.key(ServerConst.dsState).value(access.getDataService().isAcceptingRequests());
+
+        builder.key(ServerConst.dsService);
+        builder.startArray();
+
         for ( Operation operation : access.getDataService().getOperations() ) {
-            List<Endpoint> endpoints = access.getDataService().getEndpoints(operation) ;
-            describe(builder, operation, endpoints) ;
+            List<Endpoint> endpoints = access.getDataService().getEndpoints(operation);
+            describe(builder, operation, endpoints);
         }
-        builder.finishArray() ;
-        builder.finishObject() ;
+        builder.finishArray();
+        builder.finishObject();
     }
-    
+
     private static void describe(JsonBuilder builder, Operation operation, List<Endpoint> endpoints) {
-        builder.startObject() ;
-        
-        builder.key(ServerConst.srvType).value(operation.getName()) ;
-        builder.key(ServerConst.srvDescription).value(operation.getDescription()) ;
-        builder.key(ServerConst.srvEndpoints) ;
-        builder.startArray() ;
+        builder.startObject();
+
+        builder.key(ServerConst.srvType).value(operation.getName());
+        builder.key(ServerConst.srvDescription).value(operation.getDescription());
+        builder.key(ServerConst.srvEndpoints);
+        builder.startArray();
         for ( Endpoint endpoint : endpoints )
-            builder.value(endpoint.getName()) ;
-        builder.finishArray() ;
+            builder.value(endpoint.getName());
+        builder.finishArray();
 
-        builder.finishObject() ;
+        builder.finishObject();
     }
 }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/TaskBase.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/TaskBase.java
index 8b73a98..60a5aee 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/TaskBase.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/TaskBase.java
@@ -18,26 +18,26 @@
 
 package org.apache.jena.fuseki.ctl;
 
-import org.apache.jena.fuseki.servlets.HttpAction ;
-import org.apache.jena.sparql.core.DatasetGraph ;
-import org.apache.jena.sparql.core.Transactional ;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.Transactional;
 
 /** Base of async tasks - this carries some useful information around, leaving the
  * implementation of Callable.run() to the specific task.
  */
 public abstract class TaskBase implements Runnable {
-    public final long actionId ;
-    public final DatasetGraph dataset ;
-    public final String datasetName ;
-    public final Transactional transactional ;
-    
+    public final long actionId;
+    public final DatasetGraph dataset;
+    public final String datasetName;
+    public final Transactional transactional;
+
     protected TaskBase(HttpAction action) {
         // The action is closed as part of action processing so is not
         // available in the async task. Anything from it that is needed,
         // taken out here.
-        this.actionId = action.id ;
-        this.dataset = action.getDataset() ;
-        this.transactional = action.getTransactional() ; 
-        this.datasetName = action.getDatasetName() ;
+        this.actionId = action.id;
+        this.dataset = ActionCtl.getItemDataset(action);
+        this.transactional = dataset;
+        this.datasetName = ActionCtl.getItemDatasetName(action);
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
index 84990cc..fd6bc03 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
@@ -18,21 +18,21 @@
 
 package org.apache.jena.fuseki.jetty;
 
-import static java.lang.String.format ;
+import static java.lang.String.format;
 
-import java.io.* ;
+import java.io.*;
 
-import javax.servlet.http.HttpServletRequest ;
-import javax.servlet.http.HttpServletResponse ;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
-import org.apache.jena.atlas.io.IO ;
-import org.apache.jena.fuseki.servlets.ServletOps ;
-import org.apache.jena.web.HttpSC ;
-import org.eclipse.jetty.http.HttpMethod ;
-import org.eclipse.jetty.http.MimeTypes ;
-import org.eclipse.jetty.server.Request ;
-import org.eclipse.jetty.server.Response ;
-import org.eclipse.jetty.server.handler.ErrorHandler ;
+import org.apache.jena.atlas.io.IO;
+import org.apache.jena.fuseki.servlets.ServletOps;
+import org.apache.jena.web.HttpSC;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ErrorHandler;
 
 /** The usual Fuseki error handler.
  *  Outputs a plain text message.
@@ -41,41 +41,37 @@ import org.eclipse.jetty.server.handler.ErrorHandler ;
 public class FusekiErrorHandler extends ErrorHandler
 {
     public FusekiErrorHandler() {}
-    
+
     @Override
-    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
-    {
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
         String method = request.getMethod();
-     
+
         if ( !method.equals(HttpMethod.GET.asString())
              && !method.equals(HttpMethod.POST.asString())
              && !method.equals(HttpMethod.HEAD.asString()) )
-            return ;
-        
-        response.setContentType(MimeTypes.Type.TEXT_PLAIN_UTF_8.asString()) ;
-        ServletOps.setNoCache(response) ;
-        
-        ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024) ;
+            return;
+
+        response.setContentType(MimeTypes.Type.TEXT_PLAIN_UTF_8.asString());
+        ServletOps.setNoCache(response);
+
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024);
         try ( Writer writer = IO.asUTF8(bytes) ) {
             String reason = (response instanceof Response) ? ((Response)response).getReason() : null;
-            handleErrorPage(request, writer, response.getStatus(), reason) ;
+            handleErrorPage(request, writer, response.getStatus(), reason);
             writer.flush();
-            response.setContentLength(bytes.size()) ;
-            response.getOutputStream().write(bytes.toByteArray()) ;
+            response.setContentLength(bytes.size());
+            response.getOutputStream().write(bytes.toByteArray());
         }
     }
-    
+
     @Override
-    protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
-        throws IOException
-    {
+    protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) throws IOException {
         if ( message == null )
-            message = HttpSC.getMessage(code) ;
-        writer.write(format("Error %d: %s\n", code, message)) ;
-        
+            message = HttpSC.getMessage(code);
+        writer.write(format("Error %d: %s\n", code, message));
+
         Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception");
-        while(th!=null)
-        {
+        while (th != null) {
             writer.write("\n");
             StringWriter sw = new StringWriter();
             PrintWriter pw = new PrintWriter(sw);
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler1.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler1.java
index bc9eba0..7210afc 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler1.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler1.java
@@ -18,40 +18,39 @@
 
 package org.apache.jena.fuseki.jetty;
 
-import static java.lang.String.format ;
+import static java.lang.String.format;
 
-import java.io.IOException ;
+import java.io.IOException;
 
-import javax.servlet.http.HttpServletRequest ;
-import javax.servlet.http.HttpServletResponse ;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
-import org.apache.jena.fuseki.servlets.ServletOps ;
-import org.apache.jena.web.HttpSC ;
-import org.eclipse.jetty.http.HttpMethod ;
-import org.eclipse.jetty.http.MimeTypes ;
-import org.eclipse.jetty.server.Request ;
-import org.eclipse.jetty.server.Response ;
-import org.eclipse.jetty.server.handler.ErrorHandler ;
+import org.apache.jena.fuseki.servlets.ServletOps;
+import org.apache.jena.web.HttpSC;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ErrorHandler;
 
 /** One line Fuseki error handler */
 public class FusekiErrorHandler1 extends ErrorHandler
 {
     public FusekiErrorHandler1() {}
-    
+
     @Override
-    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
-    {
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
         String method = request.getMethod();
-     
-        if ( !method.equals(HttpMethod.GET.asString())
-             && !method.equals(HttpMethod.POST.asString())
-             && !method.equals(HttpMethod.HEAD.asString()) )
-            return ;
-        
-        response.setContentType(MimeTypes.Type.TEXT_PLAIN_UTF_8.asString()) ;
-        ServletOps.setNoCache(response) ;
-        int code = response.getStatus() ;
-        String message=(response instanceof Response)?((Response)response).getReason(): HttpSC.getMessage(code) ;
-        response.getOutputStream().print(format("Error %d: %s\n", code, message)) ;
+
+        if ( !method.equals(HttpMethod.GET.asString()) 
+            && !method.equals(HttpMethod.POST.asString()) 
+            && !method.equals(HttpMethod.HEAD.asString()) )
+            return;
+
+        response.setContentType(MimeTypes.Type.TEXT_PLAIN_UTF_8.asString());
+        ServletOps.setNoCache(response);
+        int code = response.getStatus();
+        String message = (response instanceof Response) ? ((Response)response).getReason() : HttpSC.getMessage(code);
+        response.getOutputStream().print(format("Error %d: %s\n", code, message));
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java
index 5ca3a91..5723445 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java
@@ -30,7 +30,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
  * It sets up "http" to redirect to "https".
  */
 public class JettyHttps {
-    
+
     /*
     * Useful documentation:
     *   http://www.eclipse.org/jetty/documentation/current/configuring-ssl.html
@@ -46,8 +46,8 @@ public class JettyHttps {
 
     /**
      * Create a HTTPS Jetty server for the {@link ServletContextHandler}
-     * <p>  
-     * If httpPort is -1, don't add http otherwise make http redirect to https. 
+     * <p>
+     * If httpPort is -1, don't add http otherwise make http redirect to https.
      */
     public static Server jettyServerHttps(ServletContextHandler handler, String keystore, String certPassword, int httpPort, int httpsPort) {
         // Server handling http and https.
@@ -61,10 +61,10 @@ public class JettyHttps {
         JettyLib.addHandler(jettyServer, handler);
         return jettyServer;
     }
-    
+
     /** Build the server - http and https connectors.
      * If httpPort is -1, don't add http.
-     */ 
+     */
     private static Server server(String keystore, String certPassword, int httpPort, int httpsPort) {
         Server server = new Server();
         if ( httpPort > 0 ) {
@@ -88,13 +88,13 @@ public class JettyHttps {
         plainConnector.setPort(httpPort);
         return plainConnector;
     }
-    
+
     /** Add HTTPS to a {@link Server}. */
     private static ServerConnector httpsConnector(Server server, int httpsPort, String keystore, String certPassword) {
         SslContextFactory sslContextFactory = new SslContextFactory();
         sslContextFactory.setKeyStorePath(keystore);
         sslContextFactory.setKeyStorePassword(certPassword);
-        
+
         SecureRequestCustomizer src = new SecureRequestCustomizer();
         src.setStsMaxAge(2000);
         src.setStsIncludeSubDomains(true);
@@ -112,7 +112,7 @@ public class JettyHttps {
         return sslConnector;
     }
 
-    /** HTTP configuration with setting for Fuseki workload. No "secure" settings. */ 
+    /** HTTP configuration with setting for Fuseki workload. No "secure" settings. */
     private static HttpConfiguration httpConfiguration() {
         HttpConfiguration http_config = new HttpConfiguration();
         // Some people do try very large operations ... really, should use POST.
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
index 005914c..7a940d4 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
@@ -43,14 +43,14 @@ import org.eclipse.jetty.util.security.Password;
  *  </pre>
  */
 public class JettyLib {
-    
+
     /** Create a Jetty {@link SecurityHandler} for a specific pathSpace, e.g {@code /database}. */
     public static SecurityHandler makeSecurityHandlerForPathspec(String pathSpec, String realm, UserStore userStore) {
         ConstraintSecurityHandler sh = makeSecurityHandler(realm, userStore);
         addPathConstraint(sh, pathSpec);
         return sh;
     }
-    
+
 //    /** Create a Jetty {@link SecurityHandler} for basic authentication. */
 //    @Deprecated
 //    public static SecurityHandler makeSecurityHandlerForPathspec(String pathSpec, String realm, UserStore userStore, String role) {
@@ -59,7 +59,7 @@ public class JettyLib {
 //        addDatasetConstraint(securityHandler, pathSpec);
 //        return securityHandler;
 //    }
-    
+
     /**
      * Digest requires an extra round trip so it is unfriendly to API
      * or scripts that stream.
@@ -68,7 +68,7 @@ public class JettyLib {
     /** Current auth mode */
     public static AuthScheme authMode = dftAuthMode;
 
-    /** Create a Jetty {@link SecurityHandler} for basic authentication. 
+    /** Create a Jetty {@link SecurityHandler} for basic authentication.
      * See {@linkplain #addPathConstraint(ConstraintSecurityHandler, String)}
      * for adding the {@code pathspec} to apply it to.
      */
@@ -76,7 +76,7 @@ public class JettyLib {
          return makeSecurityHandler(realm, userStore, "**", authMode);
      }
 
-     /** Create a Jetty {@link SecurityHandler} for basic authentication. 
+     /** Create a Jetty {@link SecurityHandler} for basic authentication.
       * See {@linkplain #addPathConstraint(ConstraintSecurityHandler, String)}
       * for adding the {@code pathspec} to apply it to.
       */
@@ -84,7 +84,7 @@ public class JettyLib {
           return makeSecurityHandler(realm, userStore, "**", authMode);
       }
 
-      /** Create a Jetty {@link SecurityHandler} for basic authentication. 
+      /** Create a Jetty {@link SecurityHandler} for basic authentication.
      * See {@linkplain #addPathConstraint(ConstraintSecurityHandler, String)}
      * for adding the {@code pathspec} to apply it to.
      */
@@ -92,10 +92,10 @@ public class JettyLib {
         // role can be "**" for any authenticated user.
         Objects.requireNonNull(userStore);
         Objects.requireNonNull(role);
-        
+
         if ( authMode == null )
             authMode = dftAuthMode;
-        
+
         ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
 
         IdentityService identService = new DefaultIdentityService();
@@ -105,22 +105,22 @@ public class JettyLib {
         HashLoginService loginService = new HashLoginService(realm);
         loginService.setUserStore(userStore);
         loginService.setIdentityService(identService);
-        
+
         securityHandler.setLoginService(loginService);
         securityHandler.setAuthenticator( authMode == AuthScheme.BASIC ? new BasicAuthenticator() : new DigestAuthenticator() );
         if ( realm != null )
             securityHandler.setRealmName(realm);
         return securityHandler;
     }
-    
+
      public static void addPathConstraint(ConstraintSecurityHandler securityHandler, String pathSpec) {
          addPathConstraint(securityHandler, pathSpec, "**");
      }
-     
+
     public static void addPathConstraint(ConstraintSecurityHandler securityHandler, String pathSpec, String role) {
         Objects.requireNonNull(securityHandler);
         Objects.requireNonNull(pathSpec);
-        
+
         ConstraintMapping mapping = new ConstraintMapping();
         Constraint constraint = new Constraint();
         String[] roles = new String[]{role};
@@ -134,7 +134,7 @@ public class JettyLib {
 
     /**
      * Make a {@link UserStore} from a password file.
-     * {@link PropertyUserStore} for details.  
+     * {@link PropertyUserStore} for details.
      */
     public static UserStore makeUserStore(String passwordFile) {
         PropertyUserStore propertyUserStore = new PropertyUserStore();
@@ -149,7 +149,7 @@ public class JettyLib {
     public static UserStore makeUserStore(String user, String password) {
         return makeUserStore(user, password, "**");
     }
-    
+
     /** Make a {@link UserStore} for a single user,password,role*/
     public static UserStore makeUserStore(String user, String password, String role) {
         Objects.requireNonNull(user);
@@ -173,9 +173,9 @@ public class JettyLib {
         return userStore;
 
     }
-    
-    
-    
+
+
+
     /** Add or append a {@link Handler} to a Jetty {@link Server}. */
     public static void addHandler(Server server, Handler handler) {
         final Handler currentHandler = server.getHandler();
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java
index b7de091..64596a1 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java
@@ -26,31 +26,31 @@ package org.apache.jena.fuseki.jetty;
 public class JettyServerConfig
 {
     public JettyServerConfig() {}
-    
+
     /** Port to run the server service on */
-    public int port ;
-    
+    public int port;
+
     /** Path to run the server service under */
-    public String contextPath ;
-    
+    public String contextPath;
+
     /** Jetty config file - if null, use the built-in configuration of Jetty */
-    public String jettyConfigFile = null ;
-    
+    public String jettyConfigFile = null;
+
     /** Listen only on the loopback (localhost) interface */
-    public boolean loopback = false ;
-    
+    public boolean loopback = false;
+
     /** Enable Accept-Encoding compression. Set to false by default.*/
-    public boolean enableCompression = false ;
-    
+    public boolean enableCompression = false;
+
     /** Enable additional logging */
-    public boolean verboseLogging = false ;
-    
+    public boolean verboseLogging = false;
+
     /** Authentication config file used to setup Jetty Basic auth,
      * if a Jetty config file was set this is ignored since Jetty config
      * allows much more complex auth methods to be implemented.
-     * Using Apache Shiro is better as well. 
+     * Using Apache Shiro is better as well.
      */
-    public String authConfigFile ;
+    public String authConfigFile;
 
 }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/package-info.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/package-info.java
index 70bda0e..1cd88dc8 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/package-info.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/package-info.java
@@ -34,13 +34,13 @@
  * <li><em>FusekiFilter</em> :: Routes requests to Fuseki (handles the dynamic nature dataset naming) by calling ServiceRouter.</li>
  * <li><em>ServiceRouterServlet</em> :: Routes requests to the appropriate service (i.e. implementing servlet).</li>
  * <li><em>ActionBase</em> :: Creates a basic {@code HttpAction} and defines {@code execCommonWorker}.</li>
- * <li><em>ActionService</em> :: Fills in {@code HttpAction} with dataset and endpoint. Calls {@code setRequest} on an {@code HttpAction}. 
+ * <li><em>ActionService</em> :: Fills in {@code HttpAction} with dataset and endpoint. Calls {@code setRequest} on an {@code HttpAction}.
  * It implements {@code execCommonWorker} as a lifecycle =&gt; {@code executeAction} =&gt; {@code executeLifecycle} =&gt; {@code validate - perform}
  * <li><em>ServiceRouter</em> :: Routing of request to the cocrete servlet implementations.
- * </ul> 
+ * </ul>
  * <p>
  * <pre>
- * ServiceDispatchServlet &lt; ActionService &lt; ActionBase 
+ * ServiceDispatchServlet &lt; ActionService &lt; ActionBase
  * Services               &lt; ActionService &lt; ActionBase
  * Admin operations       &lt; ActionCtl    &lt; ActionBase
  * Task management        &lt; ActionTasks  &lt; ActionBase
@@ -52,7 +52,7 @@
  * <ul>
  * <li><em>ContentTypeToOperation</em>:: Map&lt;content-type, Operation&gt;</li>
  * <li><em>ContentTypeToOperation</em>:: Map&lt;String, Operation&gt;</li>
- * <li><em>Dispatch</em> :: {@literal Map<Operation, ActionService>}</li> 
+ * <li><em>Dispatch</em> :: {@literal Map<Operation, ActionService>}</li>
  * </ul>
  * <p>
  */
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counter.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counter.java
index 88d4d37..4196d6e 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counter.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counter.java
@@ -18,17 +18,17 @@
 
 package org.apache.jena.fuseki.server;
 
-import java.util.concurrent.atomic.AtomicLong ;
+import java.util.concurrent.atomic.AtomicLong;
 
 /** A statistics counter */
 public class Counter
 {
-    private AtomicLong counter = new AtomicLong(0) ;
-    
+    private AtomicLong counter = new AtomicLong(0);
+
     public Counter()    {}
-    
-    public void inc()   { counter.incrementAndGet() ; } 
-    public void dec()   { counter.decrementAndGet() ; } 
-    public long value() { return counter.get() ; } 
+
+    public void inc()   { counter.incrementAndGet(); }
+    public void dec()   { counter.decrementAndGet(); }
+    public long value() { return counter.get(); }
 }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterName.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterName.java
index 7ad8cfd..1715548 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterName.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterName.java
@@ -20,24 +20,24 @@ package org.apache.jena.fuseki.server;
 
 import java.util.Objects;
 
-/** Names for all counters */ 
+/** Names for all counters */
 public class CounterName {
- // Create intern'ed symbols. 
+ // Create intern'ed symbols.
     static private NameMgr<CounterName> mgr = new NameMgr<>();
     static public CounterName register(String name, String hierarchicalName) {
         Objects.requireNonNull(name, "name");
         Objects.requireNonNull(hierarchicalName, "hierarchicalName");
         return mgr.register(name, (n)->new CounterName(name, hierarchicalName));
     }
-    
+
     // The "name" is used as a JSON key string.
     // Legacy from when this was an enum and the name() was used for the UI.
     // The better hierarchicalName is not used but becuse this has
-    // leaked to the jaavscript, we're a bit stuck. 
+    // leaked to the jaavscript, we're a bit stuck.
+
+    private final String name;
+    private final String hierarchicalName;
 
-    private final String name ;
-    private final String hierarchicalName ;
-    
     // There are generic names - apply to all services and datasets.
     // Total request received
     public static final CounterName Requests         = register("Requests", "requests");
@@ -91,16 +91,16 @@ public class CounterName {
     public static final CounterName HTTPoptions      = register("HTTPoptions", "http.options.requests");
     public static final CounterName HTTPoptionsGood  = register("HTTPoptionsGood", "http.options.requests.good");
     public static final CounterName HTTPoptionsBad   = register("HTTPoptionsBad", "http.options.requests.bad");
-    
+
     private CounterName(String name, String hierarchicalName) {
         this.name = name;
         this.hierarchicalName = hierarchicalName;
     }
-    
+
     public String getName() {
         return name;
     }
-    
+
     public String getFullName() {
         return hierarchicalName;
     }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterSet.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterSet.java
index 9b8231e..a5ae011 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterSet.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterSet.java
@@ -16,55 +16,52 @@
  * limitations under the License.
  */
 
-package org.apache.jena.fuseki.server ;
+package org.apache.jena.fuseki.server;
 
-import java.util.Collection ;
-import java.util.HashMap ;
-import java.util.Map ;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
-import org.slf4j.Logger ;
-import org.slf4j.LoggerFactory ;
+import org.apache.jena.atlas.logging.Log;
 
 /** A collection of counters */
 public class CounterSet {
-    private static Logger             log      = LoggerFactory.getLogger(CounterSet.class) ;
-
-    private Map<CounterName, Counter> counters = new HashMap<>() ;
+    private Map<CounterName, Counter> counters = new HashMap<>();
 
     public CounterSet() {}
 
     public Collection<CounterName> counters() {
-        return counters.keySet() ;
+        return counters.keySet();
     }
 
     public void inc(CounterName c) {
-        get(c).inc() ;
+        get(c).inc();
     }
 
     public void dec(CounterName c) {
-        get(c).dec() ;
+        get(c).dec();
     }
 
     public long value(CounterName c) {
-        return get(c).value() ;
+        return get(c).value();
     }
 
     public void add(CounterName counterName) {
         if ( counters.containsKey(counterName) ) {
-            log.warn("Duplicate counter in counter set: " + counterName) ;
-            return ;
+            Log.warn(CounterSet.class, "Duplicate counter in counter set: " + counterName);
+            return;
         }
-        counters.put(counterName, new Counter()) ;
+        counters.put(counterName, new Counter());
     }
 
     public boolean contains(CounterName cn) {
-        return counters.containsKey(cn) ;
+        return counters.containsKey(cn);
     }
 
     public Counter get(CounterName cn) {
-        Counter c = counters.get(cn) ;
+        Counter c = counters.get(cn);
         if ( c == null )
-            log.warn("No counter in counter set: " + cn) ;
-        return c ;
+            Log.warn(CounterSet.class, "No counter in counter set: " + cn);
+        return c;
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counters.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counters.java
index 4e5ca4b..45f3996 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counters.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counters.java
@@ -18,8 +18,8 @@
 
 package org.apache.jena.fuseki.server;
 
-/** Objects that have a counter set */ 
+/** Objects that have a counter set */
 public interface Counters {
-    public  CounterSet getCounters() ;
+    public  CounterSet getCounters();
 }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPoint.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPoint.java
index 9157353..43d8c6d 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPoint.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPoint.java
@@ -18,45 +18,52 @@
 
 package org.apache.jena.fuseki.server;
 
-import java.util.concurrent.atomic.AtomicLong ;
+import java.util.concurrent.atomic.AtomicLong;
 
-import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.HttpAction;
 
-/** A name in the URL space of the server */
+/**
+ * A pairing of name and {@link DataService}, a dataset and its endpoints (which may
+ * in turn be named), in the URL space of the server
+ */
 public class DataAccessPoint {
-    private final String name ;
-    private final DataService dataService ;
-    private AtomicLong requests = new AtomicLong(0) ;
-    
+    private final String name;
+    private final DataService dataService;
+    private AtomicLong requests = new AtomicLong(0);
+
     public DataAccessPoint(String name, DataService dataService) {
-        this.name = canonical(name) ;
-        this.dataService = dataService ;
+        this.name = canonical(name);
+        this.dataService = dataService;
         dataService.noteDataAccessPoint(this);
     }
-    
-    public String getName()     { return name ; }
-    
+
+    public String getName()     { return name; }
+
     public static String canonical(String datasetPath) {
         if ( datasetPath == null )
-            return datasetPath ;
+            return datasetPath;
         if ( datasetPath.equals("/") )
-            datasetPath = "" ;
+            datasetPath = "";
         else
             if ( !datasetPath.startsWith("/") )
-                datasetPath = "/" + datasetPath ;
+                datasetPath = "/" + datasetPath;
         if ( datasetPath.endsWith("/") )
-            datasetPath = datasetPath.substring(0, datasetPath.length() - 1) ;
-        return datasetPath ;
+            datasetPath = datasetPath.substring(0, datasetPath.length() - 1);
+        return datasetPath;
     }
 
     public DataService getDataService() {
         return dataService;
     }
 
-    public long requestCount()                          { return requests.get() ; }
-    
-    public void startRequest(HttpAction httpAction)     { requests.incrementAndGet() ; }
+    public long requestCount()                          { return requests.get(); }
 
-    public void finishRequest(HttpAction httpAction)    { requests.getAndDecrement() ; }
+    public void startRequest(HttpAction httpAction)     { requests.incrementAndGet(); }
+
+    public void finishRequest(HttpAction httpAction)    { requests.getAndDecrement(); }
+    
+    @Override public String toString() {
+        return "DataAccessPoint["+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 5fe5c82..eca7581 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
@@ -19,12 +19,12 @@
 package org.apache.jena.fuseki.server;
 
 import io.micrometer.core.instrument.MeterRegistry;
-import javax.servlet.ServletContext ;
+import javax.servlet.ServletContext;
 
-import org.apache.jena.atlas.lib.Registry ;
-import org.apache.jena.atlas.logging.Log ;
+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.FusekiException;
 import org.apache.jena.fuseki.metrics.FusekiRequestsMetrics;
 
 public class DataAccessPointRegistry extends Registry<String, DataAccessPoint>
@@ -34,20 +34,20 @@ public class DataAccessPointRegistry extends Registry<String, DataAccessPoint>
     public DataAccessPointRegistry(MeterRegistry meterRegistry) {
         this.meterRegistry = meterRegistry;
     }
-    
+
     public DataAccessPointRegistry(DataAccessPointRegistry other) {
         other.forEach((name, accessPoint)->register(name, accessPoint));
         this.meterRegistry = other.meterRegistry;
     }
-    
+
     // Preferred way to register. Other method for legacy.
     public void register(DataAccessPoint accessPt) {
         register(accessPt.getName(), accessPt);
     }
-    
+
     private void register(String name, DataAccessPoint accessPt) {
         if ( isRegistered(name) )
-            throw new FusekiException("Already registered: "+name) ;
+            throw new FusekiException("Already registered: "+name);
         super.put(name, accessPt);
         if (meterRegistry != null) {
             new FusekiRequestsMetrics( accessPt ).bindTo( meterRegistry );
@@ -55,30 +55,30 @@ public class DataAccessPointRegistry extends Registry<String, DataAccessPoint>
     }
     // Debugging
     public void print(String string) {
-        System.out.flush() ;
+        System.out.flush();
         if ( string == null )
-            string = "DataAccessPointRegistry" ;
-        System.out.println("== "+string) ;
+            string = "DataAccessPointRegistry";
+        System.out.println("== "+string);
         this.forEach((k,ref)->{
-            System.out.printf("  (key=%s, ref=%s)\n", k, ref.getName()) ;
+            System.out.printf("  (key=%s, ref=%s)\n", k, ref.getName());
             ref.getDataService().getOperations().forEach((op)->{
                 ref.getDataService().getEndpoints(op).forEach(ep->{
-                    System.out.printf("     %s : %s\n", op, ep.getName()) ;
+                    System.out.printf("     %s : %s\n", op, ep.getName());
                 });
             });
-        }) ;
+        });
     }
 
     // The server DataAccessPointRegistry is held in the ServletContext for the server.
-    
+
     public static DataAccessPointRegistry get(ServletContext cxt) {
-        DataAccessPointRegistry registry = (DataAccessPointRegistry)cxt.getAttribute(Fuseki.attrNameRegistry) ;
+        DataAccessPointRegistry registry = (DataAccessPointRegistry)cxt.getAttribute(Fuseki.attrNameRegistry);
         if ( registry == null )
-            Log.warn(DataAccessPointRegistry.class, "No data access point registry for ServletContext") ;
-        return registry ;
+            Log.warn(DataAccessPointRegistry.class, "No data access point registry for ServletContext");
+        return registry;
     }
-    
+
     public static void set(ServletContext cxt, DataAccessPointRegistry registry) {
-        cxt.setAttribute(Fuseki.attrNameRegistry, registry) ;
+        cxt.setAttribute(Fuseki.attrNameRegistry, registry);
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
index 9791b3e..192b412 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
@@ -22,13 +22,13 @@ import static java.lang.String.format;
 import static org.apache.jena.fuseki.server.DataServiceStatus.*;
 
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
-import org.apache.jena.atlas.logging.FmtLog;
 import org.apache.jena.ext.com.google.common.collect.ArrayListMultimap;
 import org.apache.jena.ext.com.google.common.collect.ListMultimap;
-import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.ext.com.google.common.collect.Multimaps;
 import org.apache.jena.fuseki.FusekiException;
 import org.apache.jena.fuseki.auth.AuthPolicy;
 import org.apache.jena.query.TxnType;
@@ -37,9 +37,15 @@ import org.apache.jena.sparql.core.DatasetGraph;
 
 public class DataService {
     private DatasetGraph dataset;
-
-    private ListMultimap<Operation, Endpoint> operations  = ArrayListMultimap.create();
-    private Map<String, Endpoint> endpoints               = new HashMap<>();
+    
+    private EndpointSet unnamedEndpoints                      = new EndpointSet(null);
+    private Map<String, EndpointSet> namedEndpoints           = new ConcurrentHashMap<>();
+    private Map<String, Endpoint> endpoints                   = new HashMap<>();
+    // Keep a single multimap of operation->endpoints. 
+    private ListMultimap<Operation, Endpoint> operationsMap   = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
+    
+    
+    // Dataset-level authorization policy.
     private AuthPolicy authPolicy                         = null;
 
     /**
@@ -62,27 +68,14 @@ public class DataService {
         counters.add(CounterName.Requests);
         counters.add(CounterName.RequestsGood);
         counters.add(CounterName.RequestsBad);
-        // Start ACTIVE. Registration controls visibility. 
+        // Start ACTIVE. Registration controls visibility.
         goActive();
     }
 
-    /**
-     * Create a {@code DataService} that has the same dataset, same operations and
-     * endpoints as another {@code DataService}. Counters are not copied, not
-     * DataAccessPoint associations.
-     */
-    private DataService(int dummy, DataService other) {
-        // Copy non-counter state of 'other'.
-        this.dataset = other.dataset;
-        this.operations = ArrayListMultimap.create(other.operations);
-        this.endpoints = new HashMap<>(other.endpoints);
-        this.state = UNINITIALIZED;
-    }
-
     /*package*/ void noteDataAccessPoint(DataAccessPoint dap) {
         this.dataAccessPoints.add(dap);
     }
-    
+
     private String label() {
         StringJoiner sj = new StringJoiner(", ", "[", "]");
         dataAccessPoints.stream()
@@ -91,52 +84,98 @@ public class DataService {
             .forEach(sj::add);
         return sj.toString();
     }
-    
+
     public DatasetGraph getDataset() {
-        return dataset; 
+        return dataset;
     }
-    
+
+   public void addEndpointNoName(Operation operation) {
+       addEndpointNoName(operation, null);
+   }
+
+   public void addEndpointNoName(Operation operation, AuthPolicy authPolicy) {
+       addEndpoint(operation, null, authPolicy);
+   }
+
     public void addEndpoint(Operation operation, String endpointName) {
         addEndpoint(operation, endpointName, null);
     }
-    
+
     public void addEndpoint(Operation operation, String endpointName, AuthPolicy authPolicy) {
-        if ( endpointName != null && endpoints.containsKey(endpointName) ) {
-            Operation existing = endpoints.get(endpointName).getOperation();
-            FmtLog.warn(Fuseki.configLog, "Duplicate use of name '%s'. Overwriting operation %s with %s",
-                                          endpointName, operation, existing);
-        }
+        //  Operation -> endpoint.
         Endpoint endpoint = new Endpoint(operation, endpointName, authPolicy);
-        endpoints.put(endpointName, endpoint);
-        operations.put(operation, endpoint);
+        addEndpoint(endpoint);
     }
-    
-    public void removeEndpoint(Endpoint endpoint) {
-        if ( endpoint.endpointName != null )
-            endpoints.remove(endpoint.endpointName);
-        operations.remove(endpoint.getOperation(), endpoint);
+
+    /** Return the {@linkplain EndpointSet} for the operations for named use. */
+    public EndpointSet getEndpointSet(String endpointName) {
+        return namedEndpoints.get(endpointName);
     }
-    
-    public Endpoint getEndpoint(String endpointName) {
-        return endpoints.get(endpointName);
+
+    /** Return the {@linkplain EndpointSet} for the operations for unnamed use. */
+    public EndpointSet getEndpointSet() {
+        return unnamedEndpoints;
     }
 
+    /**
+     * Return a collection of all endpoints for this {@linkplain DataService}.
+     * This operation is for debug and development - it is not efficient.   
+     */
     public Collection<Endpoint> getEndpoints() {
-        return operations.values();
+        // Keep separately?
+        Set<Endpoint> x = new HashSet<>();
+        unnamedEndpoints.forEach((op,ep)->x.add(ep));
+        namedEndpoints.forEach((k,eps)->{
+            eps.forEach((op,ep)->x.add(ep));
+        });
+        return x;
     }
-    
+
     public List<Endpoint> getEndpoints(Operation operation) {
-        List<Endpoint> x = operations.get(operation);
-        if ( x == null )
-            x = Collections.emptyList();
-        return x;  
+        List<Endpoint> x = operationsMap.get(operation);
+        return x;
     }
 
     /** Return the operations available here.
      *  @see #getEndpoints(Operation) to get the endpoint list
      */
     public Collection<Operation> getOperations() {
-        return operations.keySet();
+        return operationsMap.keySet();
+    }
+
+    /** Return the operations available here.
+     *  @see #getEndpoints(Operation) to get the endpoint list
+     */
+    public boolean hasOperation(Operation operation) {
+        return operationsMap.keySet().contains(operation);
+    }
+
+    public void addEndpoint(Endpoint endpoint) {
+        addEndpoint$(endpoint);
+    }
+        
+    private void addEndpoint$(Endpoint endpoint) {
+        if ( endpoint.isUnnamed() )
+            unnamedEndpoints.put(endpoint);
+        else {
+            EndpointSet eps = namedEndpoints.computeIfAbsent(endpoint.getName(), (k)->new EndpointSet(k));
+            eps.put(endpoint);
+        }
+        // Cleaner not to have duplicates. But nice to have a (short) list that keeps the create order. 
+        if ( ! operationsMap.containsEntry(endpoint.getOperation(), endpoint) )
+            operationsMap.put(endpoint.getOperation(), endpoint);
+    }
+    
+    private void removeEndpoint$(Endpoint endpoint) {
+        if ( endpoint.isUnnamed() )
+            unnamedEndpoints.remove(endpoint);
+        else {
+            EndpointSet eps = namedEndpoints.get(endpoint.getName());
+            if ( eps == null )
+                return;
+            eps.remove(endpoint);
+        }
+        operationsMap.remove(endpoint.getOperation(), endpoint);
     }
 
     //@Override
@@ -162,12 +201,12 @@ public class DataService {
     public boolean isAcceptingRequests() {
         return acceptingRequests.get();
     }
-    
+
     //@Override
     public  CounterSet getCounters() { return counters; }
-    
-    //@Override 
-    public long getRequests() { 
+
+    //@Override
+    public long getRequests() {
         return counters.value(CounterName.Requests);
     }
 
@@ -207,24 +246,23 @@ public class DataService {
     public synchronized void shutdown() {
         if ( state == CLOSING )
             return;
-        Fuseki.serverLog.info(format("Shutting down data service for %s", endpoints.keySet()));
         expel(dataset);
-        dataset = null; 
+        dataset = null;
         state = CLOSED;
     }
-    
+
     private void expel(DatasetGraph database) {
         // Text databases.
-        // Close the in-JVM objects for Lucene index and databases. 
+        // Close the in-JVM objects for Lucene index and databases.
         if ( database instanceof DatasetGraphText ) {
             DatasetGraphText dbtext = (DatasetGraphText)database;
             database = dbtext.getBase();
             dbtext.getTextIndex().close();
         }
-    
+
         boolean isTDB1 = org.apache.jena.tdb.sys.TDBInternal.isTDB1(database);
         boolean isTDB2 = org.apache.jena.tdb2.sys.TDBInternal.isTDB2(database);
-        
+
         if ( ( isTDB1 || isTDB2 ) ) {
             // JENA-1586: Remove database from the process.
             if ( isTDB1 )
@@ -236,9 +274,8 @@ public class DataService {
     }
 
     public void setAuthPolicy(AuthPolicy authPolicy) { this.authPolicy = authPolicy; }
-    
+
     /** Returning null implies no authorization control */
     public AuthPolicy authPolicy() { return authPolicy; }
-
 }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataServiceStatus.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataServiceStatus.java
index 9b66a47..2004761 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataServiceStatus.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataServiceStatus.java
@@ -18,29 +18,29 @@
 
 package org.apache.jena.fuseki.server;
 
-import org.apache.jena.rdf.model.Resource ;
+import org.apache.jena.rdf.model.Resource;
 
 public enum DataServiceStatus {
-    
+
     UNINITIALIZED("Uninitialized"),
     ACTIVE("Active"),
     OFFLINE("Offline"),
     CLOSING("Closing"),
-    CLOSED("Closed") ;
-    
-    public final String name ; 
-    DataServiceStatus(String string) { name = string ; }
-    
+    CLOSED("Closed");
+
+    public final String name;
+    DataServiceStatus(String string) { name = string; }
+
     public static DataServiceStatus status(Resource r) {
         if ( FusekiVocab.stateActive.equals(r) )
-            return ACTIVE ;
+            return ACTIVE;
         if ( FusekiVocab.stateOffline.equals(r) )
-            return OFFLINE ;
+            return OFFLINE;
         if ( FusekiVocab.stateClosing.equals(r) )
-            return CLOSING ;
+            return CLOSING;
         if ( FusekiVocab.stateClosed.equals(r) )
-            return CLOSED ;
-        return null ;
+            return CLOSED;
+        return null;
     }
 }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java
deleted file mode 100644
index bf38229..0000000
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java
+++ /dev/null
@@ -1,35 +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.server;
-
-public interface DatasetMXBean
-{
-    String getName() ;
-    
-    long getRequests() ;
-    long getRequestsGood() ;
-    long getRequestsBad() ;
-    
-//    void enable() ;
-//    void disable() ;
-//    void setReadOnly() ;
-//    boolean getReadOnly() ;
-    
-}
-
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Endpoint.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Endpoint.java
index 9b95a78..7795baa 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Endpoint.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Endpoint.java
@@ -25,7 +25,7 @@ import org.apache.jena.fuseki.auth.AuthPolicy;
 
 /*
  * An {@code Endpoint} is an instance of an {@link Operation} within a {@link DataService} and has counters.
- * An {@code Endpoint} may have a name which is a path component. 
+ * An {@code Endpoint} may have a name which is a path component.
  */
 public class Endpoint implements Counters {
 
@@ -39,7 +39,7 @@ public class Endpoint implements Counters {
         this.operation = Objects.requireNonNull(operation, "operation");
         if ( operation == null )
             throw new InternalErrorException("operation is null");
-        this.endpointName = Objects.requireNonNull(endpointName, "endpointName");
+        this.endpointName = endpointName;
         this.authPolicy = requestAuth;
         // Standard counters - there may be others
         counters.add(CounterName.Requests);
@@ -60,8 +60,12 @@ public class Endpoint implements Counters {
         return operation.equals(operation);
     }
 
+    public boolean isUnnamed() {
+        return endpointName == null || endpointName.isEmpty();
+    }
+
     public String getName() {
-        return endpointName;
+        return isUnnamed() ? "" : endpointName;
     }
 
     public AuthPolicy getAuthPolicy() {
@@ -80,4 +84,8 @@ public class Endpoint implements Counters {
         return counters.value(CounterName.RequestsBad);
     }
 
+    @Override
+    public String toString() {
+        return getName()+"["+operation+"]";
+    }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/EndpointSet.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/EndpointSet.java
new file mode 100644
index 0000000..fe0edc9
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/EndpointSet.java
@@ -0,0 +1,119 @@
+/*
+ * 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.server;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+
+import org.apache.jena.atlas.logging.Log;
+import org.apache.jena.fuseki.servlets.Dispatcher;
+import org.apache.jena.fuseki.servlets.HttpAction;
+
+/** Collection of endpoints for a dispatch point.
+ * A dispatch point is a URL name, without queyr string or content-type.
+ * {@linkplain Dispatcher#chooseOperation(HttpAction)} looks at request and decides which
+ * {@linkplain Operation} is being requested.
+ * 
+ * {@linkplain Dispatcher#chooseEndpoint} looks at request,
+ * and a {@linkplain DataService}
+ * and decides the endpoint.
+ * 
+ * There can be only be one endpoint for each operation in a {@code EndpointSet}.   
+ */
+public class EndpointSet {
+    private final String name;
+    // Fast path for a set of one endpoint.
+    private Endpoint single;
+    private Map<Operation, Endpoint> endpoints = new ConcurrentHashMap<>();
+
+    public EndpointSet(String name) {
+        super();
+        this.name = name;
+        this.single = null;
+    }
+    
+    public void put(Endpoint endpoint) {
+        if ( name != null ) {
+            if ( ! endpoint.getName().equals(name) )
+                Log.warn(EndpointSet.class, "Different endpoint name: set = '"+name+"' : endpoint = '"+endpoint.getName()+"'");    
+        } else {
+            if ( ! endpoint.isUnnamed() )
+                Log.warn(EndpointSet.class, "Different endpoint name: set = '' : endpoint = '"+endpoint.getName()+"'");
+        }
+        Operation operation = endpoint.getOperation();
+        if ( endpoints.containsKey(operation) ) {
+            Log.warn(EndpointSet.class, "Redefining endpoint for "+operation); 
+        }
+        Endpoint endpointPrev = endpoints.put(operation, endpoint);
+        resetSingle();
+    }
+    
+    public void remove(Endpoint endpoint) {
+        endpoints.remove(endpoint.getOperation());
+        resetSingle();
+    }
+    
+    public Endpoint get(Operation operation) {
+        return endpoints.get(operation);
+    }
+
+    public boolean contains(Operation operation) {
+        return endpoints.containsKey(operation);
+    }
+    
+    public boolean isEmpty() {
+        return endpoints.isEmpty();
+    }
+
+    public int size() {
+        return endpoints.size();
+    }
+
+    public void forEach(BiConsumer<Operation, Endpoint> action) { endpoints.forEach(action); }
+    
+    private void resetSingle() {
+        if ( endpoints.size() == 1 )
+            single = endpoints.values().iterator().next();
+        else {
+            // Set to null IFF not null. Debug point. 
+            if ( single != null)
+                single = null;
+        }
+    }
+
+    public Collection<Endpoint> endpoints() { return endpoints.values(); }
+
+    public Collection<Operation> operations() { return endpoints.keySet(); }
+    
+    /** Get the Endpoint for a singleton EndpointSet */
+    public Endpoint getOnly() {
+        return single;
+    }
+    
+    @Override
+    public String toString() {
+        String x = (name==null)?"":name;
+        StringJoiner sj = new StringJoiner(", ", "[", "]");
+        operations().forEach(op->sj.add(op.toString()));
+        return x+sj.toString(); 
+    }
+}
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInfo.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInfo.java
index 8312d1a..2800060 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInfo.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInfo.java
@@ -18,10 +18,10 @@
 
 package org.apache.jena.fuseki.server;
 
-import java.util.ArrayList ;
-import java.util.LinkedHashMap ;
-import java.util.List ;
-import java.util.Map ;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.function.Function;
 
 import org.apache.jena.atlas.logging.FmtLog;
@@ -29,7 +29,7 @@ import org.apache.jena.fuseki.Fuseki;
 import org.slf4j.Logger;
 
 public class FusekiInfo {
-    
+
     public static void info(FusekiInitialConfig serverConfig, DataAccessPointRegistry registry) {
         if ( ! serverConfig.verbose )
             return;
@@ -38,21 +38,21 @@ public class FusekiInfo {
 
         Logger log = Fuseki.serverLog;
         FmtLog.info(log,  "Apache Jena Fuseki");
-        
+
         // Dataset -> Endpoints
         Map<String, List<String>> z = description(registry);
-        
+
 //        if ( serverConfig.empty ) {
-//            FmtLog.info(log, "No SPARQL datasets services"); 
+//            FmtLog.info(log, "No SPARQL datasets services");
 //        } else {
 //            if ( serverConfig.datasetPath == null && serverConfig.serverConfig == null )
 //                log.error("No dataset path nor server configuration file");
 //        }
-        
+
         if ( serverConfig.datasetPath != null ) {
             if ( z.size() != 1 )
                 log.error("Expected only one dataset");
-            List<String> endpoints = z.get(serverConfig.datasetPath); 
+            List<String> endpoints = z.get(serverConfig.datasetPath);
             FmtLog.info(log,  "Dataset Type = %s", serverConfig.datasetDescription);
             FmtLog.info(log,  "Path = %s; Services = %s", serverConfig.datasetPath, endpoints);
         }
@@ -65,7 +65,7 @@ public class FusekiInfo {
         }
         FusekiInfo.logDetails(log);
     }
-    
+
     private static Map<String, List<String>> description(DataAccessPointRegistry reg) {
         Map<String, List<String>> desc = new LinkedHashMap<>();
         reg.forEach((ds,dap)->{
@@ -77,13 +77,13 @@ public class FusekiInfo {
                     String x = ep.getName();
                     if ( x.isEmpty() )
                         x = "quads";
-                    endpoints.add(x);   
+                    endpoints.add(x);
                 });
             });
         });
         return desc;
     }
-    
+
     public static void logDetails(Logger log) {
         long maxMem = Runtime.getRuntime().maxMemory();
         long totalMem = Runtime.getRuntime().totalMemory();
@@ -97,7 +97,7 @@ public class FusekiInfo {
         FmtLog.info(log, "  Memory: max=%s", f.apply(maxMem));
         FmtLog.info(log, "  OS:     %s %s %s", System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"));
     }
-    
+
     public static void logDetailsVerbose(Logger log) {
         logDetails(log);
         logOne(log, "java.vendor");
@@ -111,7 +111,7 @@ public class FusekiInfo {
         logOne(log, "user.dir");
         //logOne(log, "file.encoding");
     }
-    
+
     private static void logOne(Logger log, String property) {
         FmtLog.info(log, "    %-20s = %s", property, System.getProperty(property));
     }
@@ -129,7 +129,7 @@ public class FusekiInfo {
             return String.format("%.1fG", x/(1024.0*1024*1024));
         return String.format("%.1fT", x/(1024.0*1024*1024*1024));
     }
-    
+
 
     /** Create a human-friendly string for a number based on Kilo/Mega/Giga/Tera (powers of 10) */
     public static String strNum10(long x) {
@@ -143,7 +143,7 @@ public class FusekiInfo {
             return String.format("%.1fG", x/(1000.0*1000*1000));
         return String.format("%.1fT", x/(1000.0*1000*1000*1000));
     }
-    
+
     /** Create a human-friendly string for a number based on Kibi/Mebi/Gibi/Tebi (powers of 2) */
     public static String strNum2(long x) {
         // https://en.wikipedia.org/wiki/Kibibyte
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInitialConfig.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInitialConfig.java
index 5c44802..2eb0ecb 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInitialConfig.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInitialConfig.java
@@ -18,45 +18,45 @@
 
 package org.apache.jena.fuseki.server;
 
-import java.util.HashMap ;
-import java.util.Map ;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.apache.jena.fuseki.Fuseki;
-import org.apache.jena.sparql.core.DatasetGraph ;
+import org.apache.jena.sparql.core.DatasetGraph;
 
 /** Dataset setup (command line, config file) for a dataset (or several if config file) */
 public class FusekiInitialConfig {
-    public boolean quiet = false ;
-    public boolean verbose = Fuseki.verboseLogging ;
-    
-    // Priority order : --conf, templated  
+    public boolean quiet = false;
+    public boolean verbose = Fuseki.verboseLogging;
+
+    // Priority order : --conf, templated
     // through the command line processing should not allow --conf and a templated /dataset.
-    
+
     // Label for dataset setup (command line).
-    public String datasetDescription  = null ;
+    public String datasetDescription  = null;
     // Either this ... command line ...
-    public String    argTemplateFile  = null ;              // Command list args --mem, --loc, --memtdb
-    public String    datasetPath      = null ;              // Dataset name on the command line.
-    public boolean   allowUpdate      = false ;             // Command line --update.
+    public String    argTemplateFile  = null;              // Command list args --mem, --loc, --memtdb
+    public String    datasetPath      = null;              // Dataset name on the command line.
+    public boolean   allowUpdate      = false;             // Command line --update.
     // Special case - prebuilt dataset.  Uses datasetPath.
-    public DatasetGraph dsg           = null ;             // Embedded or command line --file)
-    
-    // Or configuration file from command line 
-    public String    fusekiCmdLineConfigFile = null ;       // Command line --conf.
+    public DatasetGraph dsg           = null;             // Embedded or command line --file)
+
+    // Or configuration file from command line
+    public String    fusekiCmdLineConfigFile = null;       // Command line --conf.
     // Or configuration from run area (lowest priority)
-    public String    fusekiServerConfigFile = null ;        // "run" area
-    
+    public String    fusekiServerConfigFile = null;        // "run" area
+
     // Additional information.
-    public Map<String,String> params  = new HashMap<>() ;
-    
+    public Map<String,String> params  = new HashMap<>();
+
     public FusekiInitialConfig() {}
-    
+
     public void reset() {
-        argTemplateFile  = null ;
-        datasetPath = null ;
-        allowUpdate = false ;
-        dsg = null ;
-        fusekiCmdLineConfigFile = null ;       // Command line --conf.
-        fusekiServerConfigFile = null ;    
+        argTemplateFile  = null;
+        datasetPath = null;
+        allowUpdate = false;
+        dsg = null;
+        fusekiCmdLineConfigFile = null;       // Command line --conf.
+        fusekiServerConfigFile = null;
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
index 54c1858..a2c0c55 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
@@ -34,12 +34,12 @@ public class FusekiVocab
 
     public static final Property pServices          = property("services");
     public static final Property pServiceName       = property("name");
-    
+
     public static final Property pAllowedUsers      = property("allowedUsers");
     public static final Property pPasswordFile      = property("passwd");
     public static final Property pRealm             = property("realm");
     public static final Property pAuth              = property("auth");
-    
+
     // Server endpoints.
     public static final Property pServerPing        = property("pingEP");
     public static final Property pServerStats       = property("statsEP");
@@ -54,33 +54,32 @@ public class FusekiVocab
 
     public static final Property pAllowTimeoutOverride          = property("allowTimeoutOverride");
     public static final Property pMaximumTimeoutOverride        = property("maximumTimeoutOverride");
-    
+
     // Internal
-    
+
     private static final String stateNameActive     = DataServiceStatus.ACTIVE.name;
     private static final String stateNameOffline    = DataServiceStatus.OFFLINE.name;
     private static final String stateNameClosing    = DataServiceStatus.CLOSING.name;
     private static final String stateNameClosed     = DataServiceStatus.CLOSED.name;
-    
+
     public static final Resource stateActive        = resource(stateNameActive);
     public static final Resource stateOffline       = resource(stateNameOffline);
     public static final Resource stateClosing       = resource(stateNameClosing);
     public static final Resource stateClosed        = resource(stateNameClosed);
-    
+
 //    public static final Property pStatus            = property("status");
 
     private static Resource resource(String localname) { return model.createResource(iri(localname)); }
     private static Property property(String localname) { return model.createProperty(iri(localname)); }
-        
-    private static String iri(String localname)
-    {
-        String uri = NS+localname;
+
+    private static String iri(String localname) {
+        String uri = NS + localname;
         IRI iri = IRIResolver.parseIRI(uri);
         if ( iri.hasViolation(true) )
             throw new FusekiException("Bad IRI: "+iri);
         if ( ! iri.isAbsolute() )
             throw new FusekiException("Bad IRI: "+iri);
-        
+
         return uri;
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/NameMgr.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/NameMgr.java
index d0d97d0..eddad0a 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/NameMgr.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/NameMgr.java
@@ -32,11 +32,11 @@ import java.util.function.Function;
  */
 public class NameMgr<T> {
     private final Map<String, T> registered = new ConcurrentHashMap<>();
-    
+
     /** register, creating an object is necessary */
     public T register(String name, Function<String, T> maker) {
         return registered.computeIfAbsent(name, maker);
     }
-    
+
     public NameMgr() { }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Operation.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Operation.java
index f9afc8a..1d0694e 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Operation.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Operation.java
@@ -18,46 +18,41 @@
 
 package org.apache.jena.fuseki.server;
 
-import org.apache.jena.fuseki.servlets.ServiceDispatchRegistry;
+import org.apache.jena.fuseki.servlets.OperationRegistry;
 
 /**
- * Operations are symbol to look up in the {@link ServiceDispatchRegistry#operationToHandler} map. The name
+ * Operations are symbol to look up in the {@link OperationRegistry#operationToHandler} map. The name
  * of an {@code Operation} is not related to the service name used to invoke the operation
  * which is determined by the {@link Endpoint}.
  */
 public class Operation {
-    
-    /** Create/intern. */ 
-    static private NameMgr<Operation> mgr = new NameMgr<>(); 
+
+    /** Create/intern. */
+    static private NameMgr<Operation> mgr = new NameMgr<>();
     static public Operation register(String name, String description) {
         return mgr.register(name, (x)->create(x, description));
     }
-    
+
     /** Create; not registered */
     static private Operation create(String name, String description) {
         return new Operation(name, description);
     }
-    
+
     public static final Operation Query          = register("Query", "SPARQL Query");
     public static final Operation Update         = register("Update", "SPARQL Update");
     public static final Operation Upload         = register("Upload", "File Upload");
+    public static final Operation Patch          = register("Patch", "RDF Patch");
     public static final Operation GSP_R          = register("GSP_R", "Graph Store Protocol (Read)");
     public static final Operation GSP_RW         = register("GSP_RW", "Graph Store Protocol");
-    public static final Operation Quads_R        = register("Quads_R", "HTTP Quads (Read)");
-    public static final Operation Quads_RW       = register("Quads_RW", "HTTP Quads");
-    
-    // Plain REST operations on the dataset URL 
-    public static final Operation DatasetRequest_R  = Quads_R;
-    public static final Operation DatasetRequest_RW = Quads_RW;
-    
-    private final String description ;
-    private final String name ;
+
+    private final String description;
+    private final String name;
 
     private Operation(String name, String description) {
         this.name = name;
         this.description = description;
     }
-    
+
     public String getName() {
         return name;
     }
@@ -65,7 +60,7 @@ public class Operation {
     public String getDescription() {
         return description;
     }
-    
+
     @Override
     public int hashCode() {
         final int prime = 31;
@@ -73,10 +68,10 @@ public class Operation {
         result = prime * result + ((name == null) ? 0 : name.hashCode());
         return result;
     }
-    
+
     // Could be this == obj
     // because we intern'ed the object
-    
+
     @Override
     public boolean equals(Object obj) {
         if ( this == obj )
@@ -93,7 +88,7 @@ public class Operation {
             return false;
         return true;
     }
- 
+
     @Override
     public String toString() {
         return name;
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/RequestLog.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/RequestLog.java
index a5fc722..86a1ff3 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/RequestLog.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/RequestLog.java
@@ -18,20 +18,20 @@
 
 package org.apache.jena.fuseki.server;
 
-import java.text.DateFormat ;
-import java.text.SimpleDateFormat ;
-import java.util.Collection ;
-import java.util.Date ;
-import java.util.Enumeration ;
-import java.util.TimeZone ;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.TimeZone;
 
-import javax.servlet.http.HttpServletRequest ;
-import javax.servlet.http.HttpServletResponse ;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
-import org.apache.jena.atlas.logging.Log ;
-import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.atlas.logging.Log;
+import org.apache.jena.fuseki.servlets.HttpAction;
 
-/** Create standard request logs (NCSA etc) */ 
+/** Create standard request logs (NCSA etc) */
 public class RequestLog {
     /*
       http://httpd.apache.org/docs/current/mod/mod_log_config.html
@@ -52,100 +52,102 @@ NCSA extended/combined log format
         Headers.
         %{}i for request header.
         %{}o for response header.
-      */  
-    
-    private static DateFormat dateFormatter ; 
+      */
+
+    private static DateFormat dateFormatter;
     static {
-        dateFormatter = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z") ;
+        dateFormatter = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z");
         dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
     }
 
-    /** NCSA combined log format *
+    /**
+     *  NCSA combined log format
+     *
      * LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %&gt;s %b  \"%{Referer}i\" \"%{User-Agent}i\"" combinedfwd
-     * XXX.XXX.XXX.XXX - - [01/Feb/2014:03:19:09 +0000] "GET / HTTP/1.1" 200 6190  "-" "check_http/v1.4.16 (nagios-plugins 1.4.16)"
+     * xxx.xxx.xxx.xxx - - [01/Feb/2014:03:19:09 +0000] "GET / HTTP/1.1" 200 6190  "-" "check_http/v1.4.16 (nagios-plugins 1.4.16)"
      */
     public static String combinedNCSA(HttpAction action) {
-        HttpServletRequest request = action.request ;
-        HttpServletResponse response = action.response ;
-        return combinedNCSA(request, response) ;
+        HttpServletRequest request = action.request;
+        HttpServletResponse response = action.response;
+        return combinedNCSA(request, response);
     }
-    
+
     public static String combinedNCSA(HttpServletRequest request, HttpServletResponse response) {
-        StringBuilder builder = new StringBuilder() ;
+        StringBuilder builder = new StringBuilder();
         // Remote
-        String remote = get(request, "X-Forwarded-For", request.getRemoteAddr()) ;
-        builder.append(remote) ;
-        builder.append(" ") ;
-        
+        String remote = get(request, "X-Forwarded-For", request.getRemoteAddr());
+        builder.append(remote);
+        builder.append(" ");
+
         // %l %u : User identity (unrelaible)
-        builder.append("- - ") ;
-        
+        builder.append("- - ");
+
         // %t
-        // Expensive? 
+        // Expensive?
         builder.append("[");
         // Better?
-        builder.append(dateFormatter.format(new Date())) ;
+        builder.append(dateFormatter.format(new Date()));
         builder.append("] ");
-        
+
         // "%r"
-        builder.append("\"")  ;
-        builder.append(request.getMethod()) ;
-        builder.append(" ") ;
+        builder.append("\"");
+        builder.append(request.getMethod());
+        builder.append(" ");
         // No query string - they are long and logged readably elsewhere
-        builder.append(request.getRequestURI()) ; 
-        builder.append("\"")  ;
+        builder.append(request.getRequestURI());
+        builder.append("\"");
         //%>s -- Final request status
-        builder.append(" ")  ;
-        builder.append(response.getStatus())  ;
-        
+        builder.append(" ");
+        builder.append(response.getStatus());
+
         //%b -- Size in bytes
-        builder.append(" ")  ;
+        builder.append(" ");
         //String size = getField()
-        String size = get(response, "Content-Length", "-") ;
-        builder.append(size)  ;
-        
-        // "%{Referer}i" 
-        builder.append(" \"")  ;
-        builder.append(get(request, "Referer", "")) ;
-        builder.append("\"")  ;
+        String size = get(response, "Content-Length", "-");
+        builder.append(size);
+
+        // "%{Referer}i"
+        builder.append(" \"");
+        builder.append(get(request, "Referer", ""));
+        builder.append("\"");
         // "%{User-Agent}i"
-        builder.append(" \"")  ;
-        builder.append(get(request, "User-Agent", "")) ;
-        builder.append("\"")  ;
-        
-        return builder.toString() ;
+        builder.append(" \"");
+        builder.append(get(request, "User-Agent", ""));
+        builder.append("\"");
+
+        return builder.toString();
     }
-    
+
     private static String get(HttpServletRequest request, String name, String dft) {
-        String x = get(request, name) ;
+        String x = get(request, name);
         if ( x == null )
-            x = dft ;
-        return x ;
+            x = dft;
+        return x;
     }
-    
+
     private static String get(HttpServletRequest request, String name) {
-        Enumeration<String> en = request.getHeaders(name) ;
-        if ( ! en.hasMoreElements() ) return null ;
-        String x = en.nextElement() ;
+        Enumeration<String> en = request.getHeaders(name);
+        if ( ! en.hasMoreElements() ) return null;
+        String x = en.nextElement();
         if ( en.hasMoreElements() ) {
-            Log.warn(RequestLog.class, "Multiple request header values") ;
+            Log.warn(RequestLog.class, "Multiple request header values");
         }
-        return x ;
+        return x;
     }
 
     private static String get(HttpServletResponse response, String name, String dft) {
-        String x = get(response, name) ;
+        String x = get(response, name);
         if ( x == null )
-            x = dft ;
-        return x ;
+            x = dft;
+        return x;
     }
 
-    
+
     private static String get(HttpServletResponse response, String name) {
-        Collection<String> en = response.getHeaders(name) ;
-        if ( en.isEmpty() )return null ;
-        if ( en.size() != 1 ) Log.warn(RequestLog.class, "Multiple response header values") ;
-        return response.getHeader(name) ;
+        Collection<String> en = response.getHeaders(name);
+        if ( en.isEmpty() )return null;
+        if ( en.size() != 1 ) Log.warn(RequestLog.class, "Multiple response header values");
+        return response.getHeader(name);
     }
 
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServerConst.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServerConst.java
index f8b0f91..6c3ac6a 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServerConst.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServerConst.java
@@ -18,24 +18,24 @@
 
 package org.apache.jena.fuseki.server;
 
-/** Various constants used in the API functions and JSON responses. */ 
+/** Various constants used in the API functions and JSON responses. */
 public class ServerConst {
     // Location under /$/
-    public static final String  opDump          = "dump" ;  
-    public static final String  opPing          = "ping" ;
-    public static final String  opStats         = "stats" ;
-    
+    public static final String  opDump          = "dump";
+    public static final String  opPing          = "ping";
+    public static final String  opStats         = "stats";
+
 //    // JSON constants
-    public static final String datasets         = "datasets" ;
-    public static final String operation        = "operation" ;
-    public static final String description      = "description" ;
-    public static final String endpoints        = "endpoints" ;
+    public static final String datasets         = "datasets";
+    public static final String operation        = "operation";
+    public static final String description      = "description";
+    public static final String endpoints        = "endpoints";
 
-    public static final String dsName           = "ds.name" ;
-    public static final String dsState          = "ds.state" ;
-    public static final String dsService        = "ds.services" ;
-    public static final String srvType          = "srv.type" ;
-    public static final String srvDescription   = "srv.description" ;
-    public static final String srvEndpoints     = "srv.endpoints" ;
+    public static final String dsName           = "ds.name";
+    public static final String dsState          = "ds.state";
+    public static final String dsService        = "ds.services";
+    public static final String srvType          = "srv.type";
+    public static final String srvDescription   = "srv.description";
+    public static final String srvEndpoints     = "srv.endpoints";
 
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServiceOnly.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServiceOnly.java
deleted file mode 100644
index 857fdcd..0000000
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServiceOnly.java
+++ /dev/null
@@ -1,44 +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.server;
-
-import org.apache.jena.fuseki.DEF;
-import org.apache.jena.sparql.core.DatasetGraph;
-import org.apache.jena.sparql.core.DatasetGraphSink;
-
-/** Configuration for a "no dataset" service */
-public class ServiceOnly {
-    
-    public static DataService dataService() {
-        return serviceOnlyDataService; 
-    }
-    
-    public static DataAccessPoint dataAccessPoint() {
-        return null;
-    }
-
-    private static final DataService serviceOnlyDataService;
-    static {
-        DatasetGraph dsg = new DatasetGraphSink();
-        serviceOnlyDataService = new DataService(dsg);
-        serviceOnlyDataService.addEndpoint(Operation.Query, DEF.ServiceQuery);
-        serviceOnlyDataService.addEndpoint(Operation.Query, DEF.ServiceQueryAlt);
-    }
-    private static final DataAccessPoint serviceOnlyDataAccessPoint = new DataAccessPoint("", serviceOnlyDataService);
-}
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java
index 4ba0ede..a337eda 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java
@@ -18,260 +18,49 @@
 
 package org.apache.jena.fuseki.servlets;
 
-import static java.lang.String.format ;
-
-import java.io.IOException ;
-import java.util.Enumeration ;
-import java.util.Map ;
-
-import javax.servlet.http.HttpServletRequest ;
-import javax.servlet.http.HttpServletResponse ;
-
-import org.apache.jena.atlas.RuntimeIOException ;
-import org.apache.jena.atlas.web.HttpException;
-import org.apache.jena.query.QueryCancelledException ;
-import org.apache.jena.riot.web.HttpNames ;
-import org.apache.jena.web.HttpSC ;
-import org.slf4j.Logger ;
-
-/** General request lifecycle */
-public abstract class ActionBase extends ServletBase
-{
-    protected final Logger log ;
-
-    protected ActionBase(Logger log) {
-        super() ;
-        this.log = log ;
-    }
-    
-    @Override 
-    public void init() {
-//        log.info("["+Utils.className(this)+"] ServletContextName = "+getServletContext().getServletContextName()) ;
-//        log.info("["+Utils.className(this)+"] ContextPath        = "+getServletContext().getContextPath()) ;
-    }
-    
-    /**
-     * Common framework for handling HTTP requests.
-     * @param request
-     * @param response
-     */
-    protected void doCommon(HttpServletRequest request, HttpServletResponse response)
-    {
-        try {
-            long id = allocRequestId(request, response);
-            
-            // Lifecycle
-            HttpAction action = allocHttpAction(id, request, response) ;
-
-            printRequest(action) ;
-            action.setStartTime() ;
-            
-            // The response may be changed to a HttpServletResponseTracker
-            response = action.response ;
-            initResponse(request, response) ;
-            try {
-                startRequest(action);
-                execCommonWorker(action) ;
-            } catch (QueryCancelledException ex) {
-                // To put in the action timeout, need (1) global, (2) dataset and (3) protocol settings.
-                // See
-                //    global -- cxt.get(ARQ.queryTimeout) 
-                //    dataset -- dataset.getContect(ARQ.queryTimeout)
-                //    protocol -- SPARQL_Query.setAnyTimeouts
-                
-                String message = String.format("Query timed out");
-                // Possibility :: response.setHeader("Retry-after", "600") ;    // 5 minutes
-                ServletOps.responseSendError(response, HttpSC.SERVICE_UNAVAILABLE_503, message);
-            } catch (ActionErrorException ex) {
-                if ( ex.getCause() != null )
-                    ex.getCause().printStackTrace(System.err) ;
-                // Log message done by printResponse in a moment.
-                if ( ex.getMessage() != null )
-                    ServletOps.responseSendError(response, ex.getRC(), ex.getMessage()) ;
-                else
-                    ServletOps.responseSendError(response, ex.getRC()) ;
-            } catch (HttpException ex) {
-                // Some code is passing up its own HttpException.
-                if ( ex.getMessage() == null )
-                    ServletOps.responseSendError(response, ex.getResponseCode());
-                else
-                    ServletOps.responseSendError(response, ex.getResponseCode(), ex.getMessage());
-            } catch (RuntimeIOException ex) {
-                log.warn(format("[%d] Runtime IO Exception (client left?) RC = %d : %s", id, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()), ex) ;
-                ServletOps.responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()) ;
-            } catch (Throwable ex) {
-                // This should not happen.
-                //ex.printStackTrace(System.err) ;
-                log.warn(format("[%d] RC = %d : %s", id, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()), ex) ;
-                ServletOps.responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()) ;
-            } finally {
-                action.setFinishTime() ;
-                finishRequest(action);
-            }
-            printResponse(action) ;
-            archiveHttpAction(action) ;
-        } catch (Throwable th) {
-            log.error("Internal error", th) ;
-        }
-    }
-
-    // ---- Operation lifecycle
+/**
+ * Base of all implementations of service {@link HttpAction}. This class provides the
+ * two steps execution of "validate" and "perform". 
+ * 
+ * Subclasses choose which HTTP
+ * methods they handle by implementing "execGet(HttpAction)" etc. These often call
+ * {@link #executeLifecycle} for their normal {@code HttpAction} lifecycle, for example,
+ * when GET and POST do the same steps so common "validate" and "execute".
+ * 
+ * See {@link ActionExecLib#execAction} for the common ActionProcessor execution with logging and error handling.
+ * This is used by {@link Dispatcher#dispatchAction(HttpAction)}.
+ * 
+ * See {@link ActionService} which overrides {@link #executeLifecycle} to add statistics counters.
+ * 
+ * Some operations like OPTIONS will implement differently.    
+ * 
+ * <pre>
+ * public void execGet(HttpAction action) { super.executeLifecycle(action); }
+ * </pre>  
+ * 
+ */
+public abstract class ActionBase implements ActionProcessor, ActionLifecycle {
+    protected ActionBase() { }
 
     /**
-     * Returns a fresh HTTP Action for this request.
-     * @param id the Request ID
-     * @param request HTTP request
-     * @param response HTTP response
-     * @return a new HTTP Action
+     * Subclasses must override {@code execGet}, {@code execPost} etc to say which
+     * methods they support.
+     * Typically, the implementation is a call to {@code executeLifecycle(action)}.
      */
-    protected HttpAction allocHttpAction(long id, HttpServletRequest request, HttpServletResponse response) {
-        // Need a way to set verbose logging on a per servlet and per request basis. 
-        return new HttpAction(id, log, request, response);
+    @Override
+    public void process(HttpAction action) {
+        // Enforce splitting to be "exec" for each HTTP method.
+        ActionProcessor.super.process(action);
     }
 
     /**
-     * Begin handling an {@link HttpAction}  
+     * Simple execution lifecycle for a SPARQL Request.
+     * No statistics.
+     *
      * @param action
      */
-    protected final void startRequest(HttpAction action) {
-        action.startRequest() ;
-    }
-    
-    /**
-     * Stop handling an {@link HttpAction}  
-     */
-    protected final void finishRequest(HttpAction action) {
-        action.finishRequest() ;
-    }
-    
-    /**
-     * Archives the HTTP Action.
-     * @param action HTTP Action
-     * @see HttpAction#minimize()
-     */
-    private void archiveHttpAction(HttpAction action) {
-        action.minimize() ;
-    }
-
-    /**
-     * Execute this request, which maybe a admin operation or a client request. 
-     * @param action HTTP Action
-     */
-    protected abstract void execCommonWorker(HttpAction action) ;
-    
-    /**
-     * Extract the name after the container name (servlet name).
-     * @param action an HTTP action
-     * @return item name as "/name" or {@code null}
-     */
-    protected static String extractItemName(HttpAction action) {
-//      action.log.info("context path  = "+action.request.getContextPath()) ;
-//      action.log.info("pathinfo      = "+action.request.getPathInfo()) ;
-//      action.log.info("servlet path  = "+action.request.getServletPath()) ;
-      // if /name
-      //    request.getServletPath() otherwise it's null
-      // if /*
-      //    request.getPathInfo() ; otherwise it's null.
-      
-      // PathInfo is after the servlet name. 
-      String x1 = action.request.getServletPath() ;
-      String x2 = action.request.getPathInfo() ;
-      
-      String pathInfo = action.request.getPathInfo() ;
-      if ( pathInfo == null || pathInfo.isEmpty() || pathInfo.equals("/") )
-          // Includes calling as a container. 
-          return null ;
-      String name = pathInfo ;
-      // pathInfo starts with a "/"
-      int idx = pathInfo.lastIndexOf('/') ;
-      if ( idx > 0 )
-          name = name.substring(idx) ;
-      // Returns "/name"
-      return name ; 
-  }
-
-    protected void doPatch(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "HTTP PATCH not supported");
-    }
-    
-    private void printRequest(HttpAction action) {
-        String url = ActionLib.wholeRequestURL(action.request) ;
-        String method = action.request.getMethod() ;
-
-        log.info(format("[%d] %s %s", action.id, method, url)) ;
-        if ( action.verbose ) {
-            Enumeration<String> en = action.request.getHeaderNames() ;
-            for (; en.hasMoreElements();) {
-                String h = en.nextElement() ;
-                Enumeration<String> vals = action.request.getHeaders(h) ;
-                if ( !vals.hasMoreElements() )
-                    log.info(format("[%d]   => %s", action.id, h+":")) ;
-                else {
-                    for (; vals.hasMoreElements();)
-                        log.info(format("[%d]   => %-20s %s", action.id, h+":", vals.nextElement())) ;
-                }
-            }
-        }
-    }
-
-    private void initResponse(HttpServletRequest request, HttpServletResponse response) {
-        setCommonHeaders(response) ;
-        String method = request.getMethod() ;
-        // All GET and HEAD operations are sensitive to conneg so ...
-        if ( HttpNames.METHOD_GET.equalsIgnoreCase(method) || HttpNames.METHOD_HEAD.equalsIgnoreCase(method) )
-            setVaryHeader(response) ;
-    }
-
-    private void printResponse(HttpAction action) {
-        long time = action.getTime() ;
-
-        HttpServletResponseTracker response = action.response ;
-        if ( action.verbose ) {
-            if ( action.responseContentType != null )
-                log.info(format("[%d]   <= %-20s %s", action.id, HttpNames.hContentType+":", action.responseContentType)) ;
-            if ( action.responseContentLength != -1 )
-                log.info(format("[%d]   <= %-20s %d", action.id, HttpNames.hContentLengh+":", action.responseContentLength)) ;
-            for (Map.Entry<String, String> e : action.headers.entrySet()) {
-                // Skip already printed.
-                if ( e.getKey().equalsIgnoreCase(HttpNames.hContentType) && action.responseContentType != null)
-                    continue;
-                if ( e.getKey().equalsIgnoreCase(HttpNames.hContentLengh) && action.responseContentLength != -1)
-                    continue;
-                log.info(format("[%d]   <= %-20s %s", action.id, e.getKey()+":", e.getValue())) ;
-            }
-        }
-
-        String timeStr = fmtMillis(time) ;
-
-        if ( action.message == null )
-            log.info(String.format("[%d] %d %s (%s)", action.id, action.statusCode,
-                                   HttpSC.getMessage(action.statusCode), timeStr)) ;
-        else
-            log.info(String.format("[%d] %d %s (%s)", action.id, action.statusCode, action.message, timeStr)) ;
-        
-        // See also HttpAction.finishRequest - request logging happens there.
-    }
-
-    /**
-     * <p>Given a time point, return the time as a milli second string if it is less than 1000,
-     * otherwise return a seconds string.</p>
-     * <p>It appends a 'ms' suffix when using milli seconds,
-     *  and 's' for seconds.</p>
-     * <p>For instance: </p>
-     * <ul>
-     * <li>10 emits 10 ms</li>
-     * <li>999 emits 999 ms</li>
-     * <li>1000 emits 1.000 s</li>
-     * <li>10000 emits 10.000 s</li>
-     * </ul>
-     * @param time the time in milliseconds
-     * @return the time as a display string
-     */
-
-    private static String fmtMillis(long time) {
-        // Millis only? seconds only?
-        if ( time < 1000 )
-            return String.format("%,d ms", time) ;
-        return String.format("%,.3f s", time / 1000.0) ;
+    protected void executeLifecycle(HttpAction action) {
+        validate(action);
+        execute(action);
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java
index 0c8d57c..d3784cf 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java
@@ -18,16 +18,14 @@
 
 package org.apache.jena.fuseki.servlets;
 
-public class ActionErrorException extends RuntimeException
-{
-    private final int rc ;
-    
-    public ActionErrorException(Throwable ex, String message, int rc)
-    {
-        super(message, ex) ;
-        this.rc = rc ;
+public class ActionErrorException extends RuntimeException {
+    private final int rc;
+
+    public ActionErrorException(Throwable ex, String message, int rc) {
+        super(message, ex);
+        this.rc = rc;
     }
-    
+
     public int getRC() {
         return rc;
     }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionExecLib.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionExecLib.java
new file mode 100644
index 0000000..4a347c9
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionExecLib.java
@@ -0,0 +1,310 @@
+/*
+ * 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.servlets;
+
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.jena.atlas.RuntimeIOException;
+import org.apache.jena.atlas.logging.FmtLog;
+import org.apache.jena.atlas.logging.Log;
+import org.apache.jena.atlas.web.HttpException;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.server.*;
+import org.apache.jena.query.QueryCancelledException;
+import org.apache.jena.riot.web.HttpNames;
+import org.apache.jena.web.HttpSC;
+import org.slf4j.Logger;
+
+/**
+ * Functions relating to {@link HttpAction} objects, including the standard execute with logging process ({@link #execAction})
+ */
+public class ActionExecLib {
+
+    /**
+     * Returns a fresh HTTP Action for this request.
+     * @param dap
+     * @param request HTTP request
+     * @param response HTTP response
+     * @return a new HTTP Action
+     */
+    public static HttpAction allocHttpAction(DataAccessPoint dap, Logger log, HttpServletRequest request, HttpServletResponse response) {
+        long id = allocRequestId(request, response);
+        // Need a way to set verbose logging on a per servlet and per request basis.
+        HttpAction action = new HttpAction(id, log, request, response);
+        if ( dap != null ) {
+            // TODO remove setRequest?
+            DataService dataService = dap.getDataService();
+            action.setRequest(dap, dataService);
+        }
+        return action;
+    }
+
+    /**
+     * Standard execution lifecycle for a SPARQL Request.
+     * <ul>
+     * <li>{@link #startRequest(HttpAction)}</li>
+     * <li>initial statistics,</li>
+     * <li>{@link ActionLifecycle#validate(HttpAction)} request,</li>
+     * <li>{@link ActionLifecycle#execute(HttpAction)} request,</li>
+     * <li>completion/error statistics,</li>
+     * <li>{@link #finishRequest(HttpAction)}
+     * </ul>
+     * Common process for handling HTTP requests with logging and Java error handling.
+     * @param action
+     * @param processor
+     */
+    public static void execAction(HttpAction action, ActionProcessor processor) {
+        execAction(action, ()->processor);
+    }
+
+    /** execAction, allowing for a choice of {@link ActionProcessor} within the logging and error handling. */
+    public static void execAction(HttpAction action, Supplier<ActionProcessor> processor) {
+        try {
+            logRequest(action);
+            action.setStartTime();
+            initResponse(action);
+            HttpServletResponse response = action.response;
+
+            startRequest(action);
+            
+            try {
+                // Get the processor inside the startRequest - error handling - finishRequest sequence. 
+                ActionProcessor proc = processor.get();
+                proc.process(action);
+            } catch (QueryCancelledException ex) {
+                // To put in the action timeout, need (1) global, (2) dataset and (3) protocol settings.
+                // See
+                //    global -- cxt.get(ARQ.queryTimeout)
+                //    dataset -- dataset.getContect(ARQ.queryTimeout)
+                //    protocol -- SPARQL_Query.setAnyTimeouts
+                String message = String.format("Query timed out");
+                ServletOps.responseSendError(response, HttpSC.SERVICE_UNAVAILABLE_503, message);
+            } catch (ActionErrorException ex) {
+                if ( ex.getCause() != null )
+                    Log.warn(Fuseki.serverLog, "ActionErrorException with cause", ex);
+                // Log message done by printResponse in a moment.
+                if ( ex.getMessage() != null )
+                    ServletOps.responseSendError(response, ex.getRC(), ex.getMessage());
+                else
+                    ServletOps.responseSendError(response, ex.getRC());
+            } catch (HttpException ex) {
+                // Some code is passing up its own HttpException.
+                if ( ex.getMessage() == null )
+                    ServletOps.responseSendError(response, ex.getStatusCode());
+                else
+                    ServletOps.responseSendError(response, ex.getStatusCode(), ex.getMessage());
+            } catch (RuntimeIOException ex) {
+                FmtLog.warn(action.log, ex, "[%d] Runtime IO Exception (client left?) RC = %d : %s", action.id, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage());
+                ServletOps.responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage());
+            } catch (Throwable ex) {
+                // This should not happen.
+                //ex.printStackTrace(System.err);
+                FmtLog.warn(action.log, ex, "[%d] RC = %d : %s", action.id, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage());
+                ServletOps.responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage());
+            } finally {
+                action.setFinishTime();
+                finishRequest(action);
+            }
+            logResponse(action);
+            archiveHttpAction(action);
+        } catch (Throwable th) {
+            FmtLog.error(action.log, th, "Internal error");
+        }
+    }
+
+    /**
+     * Helper method which gets a unique request ID and appends it as a header to the
+     * response
+     *
+     * @param request   HTTP Request
+     * @param response  HTTP Response
+     * @return Request ID
+     */
+    public static long allocRequestId(HttpServletRequest request, HttpServletResponse response) {
+        long id = requestIdAlloc.incrementAndGet();
+        addRequestId(response, id);
+        return id;
+    }
+
+    private static AtomicLong     requestIdAlloc = new AtomicLong(0);
+
+    /**
+     * Helper method for attaching a request ID to a response as a header
+     *
+     * @param response
+     *            Response
+     * @param id
+     *            Request ID
+     */
+    public static void addRequestId(HttpServletResponse response, long id) {
+        response.addHeader("Fuseki-Request-ID", Long.toString(id));
+    }
+
+    /**
+     * Begin handling an {@link HttpAction}
+     * @param action
+     */
+    private static void startRequest(HttpAction action) {
+        action.startRequest();
+    }
+
+    /**
+     * Stop handling an {@link HttpAction}
+     */
+    private static void finishRequest(HttpAction action) {
+        action.finishRequest();
+    }
+
+    /** Log an {@link HttpAction} request. */
+    public static void logRequest(HttpAction action) {
+        String url = ActionLib.wholeRequestURL(action.request);
+        String method = action.request.getMethod();
+
+        FmtLog.info(action.log, "[%d] %s %s", action.id, method, url);
+        if ( action.verbose ) {
+            Enumeration<String> en = action.request.getHeaderNames();
+            for (; en.hasMoreElements();) {
+                String h = en.nextElement();
+                Enumeration<String> vals = action.request.getHeaders(h);
+                if ( !vals.hasMoreElements() )
+                    FmtLog.info(action.log, "[%d]   => %s", action.id, h+":");
+                else {
+                    for (; vals.hasMoreElements();)
+                        FmtLog.info(action.log, "[%d]   => %-20s %s", action.id, h+":", vals.nextElement());
+                }
+            }
+        }
+    }
+
+    /** Log an {@link HttpAction} response. */
+    public static void logResponse(HttpAction action) {
+        long time = action.getTime();
+
+        HttpServletResponseTracker response = action.response;
+        if ( action.verbose ) {
+            if ( action.responseContentType != null )
+                FmtLog.info(action.log,"[%d]   <= %-20s %s", action.id, HttpNames.hContentType+":", action.responseContentType);
+            if ( action.responseContentLength != -1 )
+                FmtLog.info(action.log,"[%d]   <= %-20s %d", action.id, HttpNames.hContentLengh+":", action.responseContentLength);
+            for (Map.Entry<String, String> e : action.headers.entrySet()) {
+                // Skip already printed.
+                if ( e.getKey().equalsIgnoreCase(HttpNames.hContentType) && action.responseContentType != null)
+                    continue;
+                if ( e.getKey().equalsIgnoreCase(HttpNames.hContentLengh) && action.responseContentLength != -1)
+                    continue;
+                FmtLog.info(action.log,"[%d]   <= %-20s %s", action.id, e.getKey()+":", e.getValue());
+            }
+        }
+
+        String timeStr = fmtMillis(time);
+
+        if ( action.message == null )
+            FmtLog.info(action.log, "[%d] %d %s (%s)",
+                action.id, action.statusCode, HttpSC.getMessage(action.statusCode), timeStr);
+        else
+            FmtLog.info(action.log,"[%d] %d %s (%s)", action.id, action.statusCode, action.message, timeStr);
+
+        // See also HttpAction.finishRequest - request logging happens there.
+    }
+
+    /** Set headers for the response. */
+    public static void initResponse(HttpAction action) {
+        ServletBase.setCommonHeaders(action.response);
+        String method = action.request.getMethod();
+        // All GET and HEAD operations are sensitive to conneg so ...
+        if ( HttpNames.METHOD_GET.equalsIgnoreCase(method) || HttpNames.METHOD_HEAD.equalsIgnoreCase(method) )
+            ServletBase.setVaryHeader(action.response);
+    }
+
+    /**
+     * <p>Given a time point, return the time as a milli second string if it is less than 1000,
+     * otherwise return a seconds string.</p>
+     * <p>It appends a 'ms' suffix when using milli secoOnds,
+     *  and 's' for seconds.</p>
+     * <p>For instance: </p>
+     * <ul>
+     * <li>10 emits 10 ms</li>
+     * <li>999 emits 999 ms</li>
+     * <li>1000 emits 1.000 s</li>
+     * <li>10000 emits 10.000 s</li>
+     * </ul>
+     * @param time the time in milliseconds
+     * @return the time as a display string
+     */
+    private static String fmtMillis(long time) {
+        // Millis only? seconds only?
+        if ( time < 1000 )
+            return String.format("%,d ms", time);
+        return String.format("%,.3f s", time / 1000.0);
+    }
+
+    /**
+     * Archives the HTTP Action.
+     * @param action HTTP Action
+     * @see HttpAction#minimize()
+     */
+    private static void archiveHttpAction(HttpAction action) {
+        action.minimize();
+    }
+
+    /** Increment counter */
+    public static void incCounter(Counters counters, CounterName name) {
+        if ( counters == null )
+            return;
+        incCounter(counters.getCounters(), name);
+    }
+
+    /** Decrement counter */
+    public static void decCounter(Counters counters, CounterName name) {
+        if ( counters == null )
+            return;
+        decCounter(counters.getCounters(), name);
+    }
+
+    public static void incCounter(CounterSet counters, CounterName name) {
+        if ( counters == null )
+            return;
+        try {
+            if ( counters.contains(name) )
+                counters.inc(name);
+        }
+        catch (Exception ex) {
+            Fuseki.serverLog.warn("Exception on counter inc", ex);
+        }
+    }
+
+    public static void decCounter(CounterSet counters, CounterName name) {
+        if ( counters == null )
+            return;
+        try {
+            if ( counters.contains(name) )
+                counters.dec(name);
+        }
+        catch (Exception ex) {
+            Fuseki.serverLog.warn("Exception on counter dec", ex);
+        }
+    }
+
+}
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 33d51d6..171b4ab 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
@@ -21,15 +21,17 @@ package org.apache.jena.fuseki.servlets;
 import java.io.InputStream;
 import java.nio.charset.CharacterCodingException;
 
-import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 import org.apache.jena.atlas.RuntimeIOException;
-import org.apache.jena.atlas.web.AcceptList ;
+import org.apache.jena.atlas.web.AcceptList;
 import org.apache.jena.atlas.web.ContentType;
-import org.apache.jena.atlas.web.MediaType ;
-import org.apache.jena.fuseki.DEF ;
-import org.apache.jena.fuseki.server.DataAccessPoint ;
-import org.apache.jena.fuseki.server.DataAccessPointRegistry ;
+import org.apache.jena.atlas.web.MediaType;
+import org.apache.jena.fuseki.DEF;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.server.DataAccessPoint;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry;
 import org.apache.jena.fuseki.system.ConNeg;
 import org.apache.jena.fuseki.system.FusekiNetLib;
 import org.apache.jena.riot.Lang;
@@ -39,85 +41,82 @@ import org.apache.jena.riot.RiotException;
 import org.apache.jena.riot.system.ErrorHandler;
 import org.apache.jena.riot.system.ErrorHandlerFactory;
 import org.apache.jena.riot.system.StreamRDF;
+import org.apache.jena.riot.web.HttpNames;
 
 /** Operations related to servlets */
 
 public class ActionLib {
     /**
-     * A possible implementation for {@link ActionService#mapRequestToDataset}
+     * Get the datasets from an {@link HttpAction}
      * that assumes the form /dataset/service.
      * @param action the request
      * @return the dataset
      */
     public static String mapRequestToDataset(HttpAction action) {
-         String uri = action.getActionURI() ;
-         return mapActionRequestToDataset(uri) ;
+         String uri = action.getActionURI();
+         return mapActionRequestToDataset(uri);
      }
-    
+
     /** Map request to uri in the registry.
      *  A possible implementation for mapRequestToDataset(String)
-     *  that assumes the form /dataset/service 
+     *  that assumes the form /dataset/service
      *  Returning null means no mapping found.
-     *  The URI must be the action URI (no contact path) 
+     *  The URI must be the action URI (no contact path)
      */
-    
+
     public static String mapActionRequestToDataset(String uri) {
         // Chop off trailing part - the service selector
-        // e.g. /dataset/sparql => /dataset 
-        int i = uri.lastIndexOf('/') ;
+        // e.g. /dataset/sparql => /dataset
+        int i = uri.lastIndexOf('/');
         if ( i == -1 )
-            return null ;
-        if ( i == 0 )
-        {
+            return null;
+        if ( i == 0 ) {
             // started with '/' - leave.
-            return uri ;
+            return uri;
         }
-        
-        return uri.substring(0, i) ;
+
+        return uri.substring(0, i);
     }
 
-    /** Calculate the operation, given action and data access point */ 
+    /** Calculate the operation, given action and data access point */
     public static String mapRequestToOperation(HttpAction action, DataAccessPoint dsRef) {
         if ( dsRef == null )
-            return "" ;
-        String uri = action.getActionURI() ;
+            return "";
+        String uri = action.getActionURI();
         String name = dsRef.getName();
         if ( name.length() >= uri.length() )
-            return "" ;
-        return uri.substring(name.length()+1) ;   // Skip the separating "/"
-        
+            return "";
+        return uri.substring(name.length()+1);   // Skip the separating "/"
+
     }
-    
-    /** Implementation of mapRequestToDataset(String) that looks for
-     * the longest match in the registry.
-     * This includes use in direct naming GSP. 
+
+    /**
+     * Implementation of mapRequestToDataset(String) that looks for the longest match
+     * in the registry. This includes use in direct naming GSP.
      */
-    public static String mapRequestToDatasetLongest$(String uri, DataAccessPointRegistry registry) 
-    {
+    public static String mapRequestToDatasetLongest$(String uri, DataAccessPointRegistry registry) {
         if ( uri == null )
-            return null ;
-        
+            return null;
+
         // This covers local, using the URI as a direct name for
-        // a graph, not just using the indirect ?graph= or ?default 
+        // a graph, not just using the indirect ?graph= or ?default
         // forms.
 
-        String ds = null ;
+        String ds = null;
         for ( String ds2 : registry.keys() ) {
             if ( ! uri.startsWith(ds2) )
-                continue ;
+                continue;
 
-            if ( ds == null )
-            {
-                ds = ds2 ;
-                continue ; 
+            if ( ds == null ) {
+                ds = ds2;
+                continue;
             }
-            if ( ds.length() < ds2.length() )
-            {
-                ds = ds2 ;
-                continue ;
+            if ( ds.length() < ds2.length() ) {
+                ds = ds2;
+                continue;
             }
         }
-        return ds ;
+        return ds;
     }
 
     /** Calculate the fill URL including query string
@@ -126,16 +125,16 @@ public class ActionLib {
      * @return String The full URL, including query string.
      */
     public static String wholeRequestURL(HttpServletRequest request) {
-        StringBuffer sb = request.getRequestURL() ;
-        String queryString = request.getQueryString() ;
+        StringBuffer sb = request.getRequestURL();
+        String queryString = request.getQueryString();
         if ( queryString != null ) {
-            sb.append("?") ;
-            sb.append(queryString) ;
+            sb.append("?");
+            sb.append(queryString);
         }
-        return sb.toString() ;
+        return sb.toString();
     }
 
-    /* 
+    /*
      * The context path can be:
      * "" for the root context
      * "/APP" for named contexts
@@ -145,60 +144,60 @@ public class ActionLib {
      */
     public static String removeContextPath(HttpAction action) {
 
-        return actionURI(action.request) ;
+        return actionURI(action.request);
     }
-    
+
     /**
-     * @return the URI without context path of the webapp.
+     * @return the URI without context path of the webapp and without query string.
      */
     public static String actionURI(HttpServletRequest request) {
-//      Log.info(this, "URI                     = '"+request.getRequestURI()) ;
-//      Log.info(this, "Context Path            = '"+request.getContextPath()+"'") ;
-//      Log.info(this, "Servlet path            = '"+request.getServletPath()+"'") ;
-//      ServletContext cxt = this.getServletContext() ;
-//      Log.info(this, "ServletContext path     = '"+cxt.getContextPath()+"'") ;
-        
-        String contextPath = request.getServletContext().getContextPath() ;
-        String uri = request.getRequestURI() ;
+//      Log.info(this, "URI                     = '"+request.getRequestURI());
+//      Log.info(this, "Context Path            = '"+request.getContextPath()+"'");
+//      Log.info(this, "Servlet path            = '"+request.getServletPath()+"'");
+//      ServletContext cxt = this.getServletContext();
+//      Log.info(this, "ServletContext path     = '"+cxt.getContextPath()+"'");
+
+        String contextPath = request.getServletContext().getContextPath();
+        String uri = request.getRequestURI();
         if ( contextPath == null )
-            return uri ;
+            return uri;
         if ( contextPath.isEmpty())
-            return uri ;
-        String x = uri ;
+            return uri;
+        String x = uri;
         if ( uri.startsWith(contextPath) )
-            x = uri.substring(contextPath.length()) ;
-        return x ;
+            x = uri.substring(contextPath.length());
+        return x;
     }
 
-    /** Negotiate the content-type and set the response headers */ 
+    /** Negotiate the content-type and set the response headers */
     public static MediaType contentNegotation(HttpAction action, AcceptList myPrefs, MediaType defaultMediaType) {
-        MediaType mt = ConNeg.chooseContentType(action.request, myPrefs, defaultMediaType) ;
+        MediaType mt = ConNeg.chooseContentType(action.request, myPrefs, defaultMediaType);
         if ( mt == null )
-            return null ;
+            return null;
         if ( mt.getContentType() != null )
-            action.response.setContentType(mt.getContentType()) ;
+            action.response.setContentType(mt.getContentType());
         if ( mt.getCharset() != null )
-            action.response.setCharacterEncoding(mt.getCharset()) ;
-        return mt ;
+            action.response.setCharacterEncoding(mt.getCharset());
+        return mt;
     }
-    
-    /** Negotiate the content-type for an RDF triples syntax and set the response headers */ 
+
+    /** Negotiate the content-type for an RDF triples syntax and set the response headers */
     public static MediaType contentNegotationRDF(HttpAction action) {
-        return contentNegotation(action, DEF.rdfOffer, DEF.acceptRDFXML) ;
+        return contentNegotation(action, DEF.rdfOffer, DEF.acceptRDFXML);
     }
 
-    /** Negotiate the content-type for an RDF quads syntax and set the response headers */ 
+    /** Negotiate the content-type for an RDF quads syntax and set the response headers */
     public static MediaType contentNegotationQuads(HttpAction action) {
-        return contentNegotation(action, DEF.quadsOffer, DEF.acceptNQuads) ;
+        return contentNegotation(action, DEF.quadsOffer, DEF.acceptNQuads);
     }
 
-    /** 
+    /**
      * Parse RDF content
      */
     public static void parse(HttpAction action, StreamRDF dest, InputStream input, Lang lang, String base) {
         try {
             if ( ! RDFParserRegistry.isRegistered(lang) )
-                ServletOps.errorBadRequest("No parser for language '"+lang.getName()+"'") ;
+                ServletOps.errorBadRequest("No parser for language '"+lang.getName()+"'");
             ErrorHandler errorHandler = ErrorHandlerFactory.errorHandlerStd(action.log);
             RDFParser.create()
                 .errorHandler(errorHandler)
@@ -211,7 +210,7 @@ public class ActionLib {
                 throw new RiotException("Character Coding Error: "+ex.getMessage());
             throw ex;
         }
-        catch (RiotException ex) { ServletOps.errorBadRequest("Parse error: "+ex.getMessage()) ; }
+        catch (RiotException ex) { ServletOps.errorBadRequest("Parse error: "+ex.getMessage()); }
     }
 
     /** Get the content type of an action or return the default.
@@ -219,7 +218,88 @@ public class ActionLib {
      * @return ContentType
      */
     public static ContentType getContentType(HttpAction action) {
-        return FusekiNetLib.getContentType(action.request) ;
+        return FusekiNetLib.getContentType(action.request);
+    }
+    
+    public static void setCommonHeadersForOptions(HttpServletResponse httpResponse) {
+        if ( Fuseki.CORS_ENABLED )
+            httpResponse.setHeader(HttpNames.hAccessControlAllowHeaders, "X-Requested-With, Content-Type, Authorization");
+        setCommonHeaders(httpResponse);
+    }
+
+    public static void setCommonHeaders(HttpServletResponse httpResponse) {
+        if ( Fuseki.CORS_ENABLED )
+            httpResponse.setHeader(HttpNames.hAccessControlAllowOrigin, "*");
+        if ( Fuseki.outputFusekiServerHeader )
+            httpResponse.setHeader(HttpNames.hServer, Fuseki.serverHttpName);
+    }
+
+    /**
+     * Extract the name after the container name (servlet name).
+     * @param action an HTTP action
+     * @return item name as "/name" or {@code null}
+     */
+    private /*unused*/ static String extractItemName(HttpAction action) {
+//          action.log.info("context path  = "+action.request.getContextPath());
+//          action.log.info("pathinfo      = "+action.request.getPathInfo());
+//          action.log.info("servlet path  = "+action.request.getServletPath());
+        // if /name
+        //    request.getServletPath() otherwise it's null
+        // if /*
+        //    request.getPathInfo(); otherwise it's null.
+
+        // PathInfo is after the servlet name.
+        String x1 = action.request.getServletPath();
+        String x2 = action.request.getPathInfo();
+
+        String pathInfo = action.request.getPathInfo();
+        if ( pathInfo == null || pathInfo.isEmpty() || pathInfo.equals("/") )
+            // Includes calling as a container.
+            return null;
+        String name = pathInfo;
+        // pathInfo starts with a "/"
+        int idx = pathInfo.lastIndexOf('/');
+        if ( idx > 0 )
+            name = name.substring(idx);
+        // Returns "/name"
+        return name;
+    }
+
+    // Packing of OPTIONS.
+
+    public static void doOptionsGet(HttpAction action) {
+        ServletBase.setCommonHeadersForOptions(action.response);
+        action.response.setHeader(HttpNames.hAllow, "GET,OPTIONS");
+    }
+
+    public static void doOptionsGetHead(HttpAction action) {
+        ServletBase.setCommonHeadersForOptions(action.response);
+        action.response.setHeader(HttpNames.hAllow, "GET,HEAD,OPTIONS");
+    }
+
+    public static void doOptionsGetPost(HttpAction action) {
+        ServletBase.setCommonHeadersForOptions(action.response);
+        action.response.setHeader(HttpNames.hAllow, "GET,POST,OPTIONS");
+    }
+
+    public static void doOptionsGetPostHead(HttpAction action) {
+        ServletBase.setCommonHeadersForOptions(action.response);
+        action.response.setHeader(HttpNames.hAllow, "GET,POST,HEAD,OPTIONS");
+    }
+
+    public static void doOptionsGetPostDelete(HttpAction action) {
+        ServletBase.setCommonHeadersForOptions(action.response);
+        action.response.setHeader(HttpNames.hAllow, "GET,POST,DELETE,OPTIONS");
+    }
+
+    public static void doOptionsGetPostDeleteHead(HttpAction action) {
+        ServletBase.setCommonHeadersForOptions(action.response);
+        action.response.setHeader(HttpNames.hAllow, "GET,HEAD,POST,DELETE,OPTIONS");
+    }
+
+    public static void doOptionsPost(HttpAction action) {
+        ServletBase.setCommonHeadersForOptions(action.response);
+        action.response.setHeader(HttpNames.hAllow, "POST,OPTIONS");
     }
 }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLifecycle.java
similarity index 76%
rename from jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java
rename to jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLifecycle.java
index 2de7658..75fd7ef 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLifecycle.java
@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -16,10 +16,13 @@
  * limitations under the License.
  */
 
-package org.apache.jena.fuseki.server;
+package org.apache.jena.fuseki.servlets;
 
-public interface CounterMXBean
-{
-    long getValue() ; 
-}
+public interface ActionLifecycle {
+
+    /** The validation step of a request */
+    public void validate(HttpAction action);
 
+    /** The perform step of a request */
+    public void execute(HttpAction action);
+}
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
new file mode 100644
index 0000000..8c56b94
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionProcessor.java
@@ -0,0 +1,52 @@
+/*
+ * 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.servlets;
+
+import static org.apache.jena.riot.web.HttpNames.*;
+
+/** Interface for executing {@link HttpAction}. */
+public interface ActionProcessor {
+    // c.f HttpServlet.
+    /**
+     * Execute this request.
+     *
+     * @param action   HTTP Action
+     */
+    public default void process(HttpAction action) {
+        switch (action.getMethod() ) {
+            case METHOD_GET:        execGet(action);      break;
+            case METHOD_POST:       execPost(action);     break;
+            case METHOD_PATCH:      execPatch(action);    break;
+            case METHOD_PUT:        execPut(action);      break;
+            case METHOD_DELETE:     execDelete(action);   break;
+            case METHOD_HEAD:       execHead(action);     break;
+            case METHOD_OPTIONS:    execOptions(action);  break;
+            case METHOD_TRACE:      execTrace(action);    break;
+        }
+    }
+
+    public default void execHead(HttpAction action)     { ServletOps.errorMethodNotAllowed(METHOD_HEAD); }
+    public default void execGet(HttpAction action)      { ServletOps.errorMethodNotAllowed(METHOD_GET); }
+    public default void execPost(HttpAction action)     { ServletOps.errorMethodNotAllowed(METHOD_POST); }
+    public default void execPatch(HttpAction action)    { ServletOps.errorMethodNotAllowed(METHOD_PATCH); }
+    public default void execPut(HttpAction action)      { ServletOps.errorMethodNotAllowed(METHOD_PUT); }
+    public default void execDelete(HttpAction action)   { ServletOps.errorMethodNotAllowed(METHOD_DELETE); }
+    public default void execOptions(HttpAction action)  { ServletOps.errorMethodNotAllowed(METHOD_OPTIONS); }
+    public default void execTrace(HttpAction action)    { ServletOps.errorMethodNotAllowed(METHOD_TRACE); }
+}
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java
index e16e793..56f3592 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java
@@ -18,35 +18,30 @@
 
 package org.apache.jena.fuseki.servlets;
 
-import java.util.Locale ;
+import static org.apache.jena.fuseki.servlets.ActionExecLib.incCounter;
+import static org.apache.jena.riot.web.HttpNames.*;
 
-import javax.servlet.http.HttpServletRequest ;
-import javax.servlet.http.HttpServletResponse ;
+import java.util.Locale;
 
-import org.apache.jena.fuseki.server.CounterName ;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.jena.fuseki.server.CounterName;
 import org.apache.jena.sparql.core.DatasetGraph;
 
-/** Common point for operations that are "REST"ish (use GET/PUT etc as operations). */ 
+/** Common point for operations that are "REST"ish (use GET/PUT etc as operations). */
 public abstract class ActionREST extends ActionService
 {
-    public ActionREST()
-    { super() ; }
-
-    @Override
-    protected void service(HttpServletRequest request, HttpServletResponse response) {
-        // Direct all verbs to our common framework.
-        doCommon(request, response) ;
-    }
-    
-    @Override
-    protected void perform(HttpAction action) {
-        dispatch(action) ;
+    public ActionREST() {
+        super();
     }
 
-    private void dispatch(HttpAction action) {
-        HttpServletRequest req = action.request ;
-        HttpServletResponse resp = action.response ;
-        String method = req.getMethod().toUpperCase(Locale.ROOT) ;
+    @Override
+    public void execute(HttpAction action) {
+        // Intercept to put counters around calls.
+        HttpServletRequest req = action.request;
+        HttpServletResponse resp = action.response;
+        String method = req.getMethod().toUpperCase(Locale.ROOT);
 
         if (method.equals(METHOD_GET))
             doGet$(action);
@@ -55,112 +50,123 @@ public abstract class ActionREST extends ActionService
         else if (method.equals(METHOD_POST))
             doPost$(action);
         else if (method.equals(METHOD_PATCH))
-            doPatch$(action) ;
+            doPatch$(action);
         else if (method.equals(METHOD_OPTIONS))
-            doOptions$(action) ;
+            doOptions$(action);
         else if (method.equals(METHOD_TRACE))
-            //doTrace(action) ;
-            ServletOps.errorMethodNotAllowed("TRACE") ;
+            //doTrace(action);
+            ServletOps.errorMethodNotAllowed("TRACE");
         else if (method.equals(METHOD_PUT))
-            doPut$(action) ;   
+            doPut$(action);
         else if (method.equals(METHOD_DELETE))
-            doDelete$(action) ;
+            doDelete$(action);
         else
-            ServletOps.errorNotImplemented("Unknown method: "+method) ;
+            ServletOps.errorNotImplemented("Unknown method: "+method);
     }
 
     /**
      * Decide on the dataset to use for the operation. This can be overridden
-     * by specialist subclasses e.g. data access control. 
+     * by specialist subclasses e.g. data access control.
      */
     protected DatasetGraph decideDataset(HttpAction action) {
-        return action.getActiveDSG() ;
+        return action.getActiveDSG();
     }
-    
+
     // Counter wrappers
-    
+
     private final void doGet$(HttpAction action) {
-        incCounter(action.getEndpoint(), CounterName.HTTPget) ;
+        incCounter(action.getEndpoint(), CounterName.HTTPget);
         try {
-            doGet(action) ;
-            incCounter(action.getEndpoint(), CounterName.HTTPgetGood) ;
+            doGet(action);
+            incCounter(action.getEndpoint(), CounterName.HTTPgetGood);
         } catch ( ActionErrorException ex) {
-            incCounter(action.getEndpoint(), CounterName.HTTPgetBad) ;
-            throw ex ;
+            incCounter(action.getEndpoint(), CounterName.HTTPgetBad);
+            throw ex;
         }
     }
 
     private final void doHead$(HttpAction action) {
-        incCounter(action.getEndpoint(), CounterName.HTTPhead) ;
+        incCounter(action.getEndpoint(), CounterName.HTTPhead);
         try {
-            doHead(action) ;
-            incCounter(action.getEndpoint(), CounterName.HTTPheadGood) ;
+            doHead(action);
+            incCounter(action.getEndpoint(), CounterName.HTTPheadGood);
         } catch ( ActionErrorException ex) {
-            incCounter(action.getEndpoint(), CounterName.HTTPheadBad) ;
-            throw ex ;
+            incCounter(action.getEndpoint(), CounterName.HTTPheadBad);
+            throw ex;
         }
     }
 
     private final void doPost$(HttpAction action) {
-        incCounter(action.getEndpoint(), CounterName.HTTPpost) ;
+        incCounter(action.getEndpoint(), CounterName.HTTPpost);
         try {
-            doPost(action) ;
-            incCounter(action.getEndpoint(), CounterName.HTTPpostGood) ;
+            doPost(action);
+            incCounter(action.getEndpoint(), CounterName.HTTPpostGood);
         } catch ( ActionErrorException ex) {
-            incCounter(action.getEndpoint(), CounterName.HTTPpostBad) ;
-            throw ex ;
+            incCounter(action.getEndpoint(), CounterName.HTTPpostBad);
+            throw ex;
         }
     }
 
     private final void doPatch$(HttpAction action) {
-        incCounter(action.getEndpoint(), CounterName.HTTPpatch) ;
+        incCounter(action.getEndpoint(), CounterName.HTTPpatch);
         try {
-            doPatch(action) ;
-            incCounter(action.getEndpoint(), CounterName.HTTPpatchGood) ;
+            doPatch(action);
+            incCounter(action.getEndpoint(), CounterName.HTTPpatchGood);
         } catch ( ActionErrorException ex) {
-            incCounter(action.getEndpoint(), CounterName.HTTPpatchBad) ;
-            throw ex ;
+            incCounter(action.getEndpoint(), CounterName.HTTPpatchBad);
+            throw ex;
         }
     }
 
     private final void doDelete$(HttpAction action) {
-        incCounter(action.getEndpoint(), CounterName.HTTPdelete) ;
+        incCounter(action.getEndpoint(), CounterName.HTTPdelete);
         try {
-            doDelete(action) ;
-            incCounter(action.getEndpoint(), CounterName.HTTPdeleteGood) ;
+            doDelete(action);
+            incCounter(action.getEndpoint(), CounterName.HTTPdeleteGood);
         } catch ( ActionErrorException ex) {
-            incCounter(action.getEndpoint(), CounterName.HTTPdeleteBad) ;
-            throw ex ;
+            incCounter(action.getEndpoint(), CounterName.HTTPdeleteBad);
+            throw ex;
         }
     }
 
     private final void doPut$(HttpAction action) {
-        incCounter(action.getEndpoint(), CounterName.HTTPput) ;
+        incCounter(action.getEndpoint(), CounterName.HTTPput);
         try {
-            doPut(action) ;
-            incCounter(action.getEndpoint(), CounterName.HTTPputGood) ;
+            doPut(action);
+            incCounter(action.getEndpoint(), CounterName.HTTPputGood);
         } catch ( ActionErrorException ex) {
-            incCounter(action.getEndpoint(), CounterName.HTTPputBad) ;
-            throw ex ;
+            incCounter(action.getEndpoint(), CounterName.HTTPputBad);
+            throw ex;
         }
     }
 
     private final void doOptions$(HttpAction action) {
-        incCounter(action.getEndpoint(), CounterName.HTTPoptions) ;
+        incCounter(action.getEndpoint(), CounterName.HTTPoptions);
         try {
-            doOptions(action) ;
-            incCounter(action.getEndpoint(), CounterName.HTTPoptionsGood) ;
+            doOptions(action);
+            incCounter(action.getEndpoint(), CounterName.HTTPoptionsGood);
         } catch ( ActionErrorException ex) {
-            incCounter(action.getEndpoint(), CounterName.HTTPoptionsBad) ;
-            throw ex ;
+            incCounter(action.getEndpoint(), CounterName.HTTPoptionsBad);
+            throw ex;
         }
     }
-    
-    protected abstract void doGet(HttpAction action) ;
-    protected abstract void doHead(HttpAction action) ;
-    protected abstract void doPost(HttpAction action) ;
-    protected abstract void doPatch(HttpAction action) ;
-    protected abstract void doDelete(HttpAction action) ;
-    protected abstract void doPut(HttpAction action) ;
-    protected abstract void doOptions(HttpAction action) ;
+
+  protected abstract void doGet(HttpAction action);
+  protected abstract void doHead(HttpAction action);
+  protected abstract void doPost(HttpAction action);
+  protected abstract void doPut(HttpAction action);
+  protected abstract void doDelete(HttpAction action);
+  protected abstract void doPatch(HttpAction action);
+  protected abstract void doOptions(HttpAction action);
+
+  // If not final in ActionBase
+  //@Override public void process(HttpAction action)      { executeLifecycle(action); }
+
+  @Override public void execHead(HttpAction action)     { executeLifecycle(action); }
+  @Override public void execGet(HttpAction action)      { executeLifecycle(action); }
+  @Override public void execPost(HttpAction action)     { executeLifecycle(action); }
+  @Override public void execPatch(HttpAction action)    { executeLifecycle(action); }
+  @Override public void execPut(HttpAction action)      { executeLifecycle(action); }
+  @Override public void execDelete(HttpAction action)   { executeLifecycle(action); }
+  @Override public void execOptions(HttpAction action)  { executeLifecycle(action); }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
index c329cda..881493a 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
@@ -18,232 +18,24 @@
 
 package org.apache.jena.fuseki.servlets;
 
-import static java.lang.String.format;
 import static org.apache.jena.fuseki.server.CounterName.Requests;
 import static org.apache.jena.fuseki.server.CounterName.RequestsBad;
 import static org.apache.jena.fuseki.server.CounterName.RequestsGood;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.function.BiFunction;
-
-import javax.servlet.ServletException;
+import static org.apache.jena.fuseki.servlets.ActionExecLib.incCounter;
 
 import org.apache.jena.atlas.RuntimeIOException;
-import org.apache.jena.atlas.lib.InternalErrorException;
-import org.apache.jena.fuseki.Fuseki;
-import org.apache.jena.fuseki.auth.Auth;
-import org.apache.jena.fuseki.server.*;
+import org.apache.jena.fuseki.server.CounterSet;
 import org.apache.jena.query.QueryCancelledException;
-import org.apache.jena.web.HttpSC;
 
-/** Service request lifecycle */
 public abstract class ActionService extends ActionBase {
-    
-    private final BiFunction<HttpAction, Operation, ActionService> selectProcessor;
-
-    /**
-     * ActionService for a directly called serlvet. The request handler will be
-     * {@code this.executeLifecycle}.
-     */
-    protected ActionService() {
-        super(Fuseki.actionLog);
-        selectProcessor = (action, operation)->this;
-    }
-
-    /**
-     * ActionService to redirect to another ActionService to be request handler will be
-     * {@code selectProcessor.executeLifecycle}.
-     * @see ServiceRouter
-     */
-    protected ActionService(BiFunction<HttpAction, Operation, ActionService> selectProcessor) {
-        super(Fuseki.actionLog);
-        this.selectProcessor = selectProcessor;
-    }
-
-    protected abstract void validate(HttpAction action);
 
-    protected abstract void perform(HttpAction action);
-
-    /**
-     * Executes common tasks, including mapping the request to the right dataset, setting
-     * the dataset into the HTTP action, and retrieving the service for the dataset
-     * requested. Finally, it calls the {@link #executeAction(HttpAction)} method, which
-     * executes the HTTP Action life cycle.
-     */
+    /** Add counters to the validate-execute lifecycle. */
     @Override
-    final
-    protected void execCommonWorker(HttpAction action) {
-        DataAccessPoint dataAccessPoint;
-        DataService dSrv;
-
-        String datasetUri = mapRequestToDataset(action);
-        if ( datasetUri != null ) {
-            dataAccessPoint = action.getDataAccessPointRegistry().get(datasetUri);
-            
-            if ( dataAccessPoint == null ) {
-                ServletOps.errorNotFound("No dataset for URI: " + datasetUri);
-                return;
-            }
-            dSrv = dataAccessPoint.getDataService();
-
-            if ( !dSrv.isAcceptingRequests() ) {
-                ServletOps.error(HttpSC.SERVICE_UNAVAILABLE_503, "Dataset not currently active");
-                return;
-            }
-        } else {
-            // Routed to this URL; no registered dataset on this URL.
-            // e.g. General query servlet
-            dSrv = ServiceOnly.dataService();
-            dataAccessPoint = ServiceOnly.dataAccessPoint();
-        }
-
-        action.setRequest(dataAccessPoint, dSrv);
-        // Endpoint Name is "" for GSP or quads.
-        // Endpoint name is not "", but unknown for GSP direct naming (which is usually disabled).
-        String endpointName = mapRequestToOperation(action, dataAccessPoint);
-        
-        // ServiceRouter dispatch
-        Operation operation;
-        if ( !endpointName.isEmpty() ) {
-            operation = chooseOperation(action, dSrv, endpointName);
-            if ( operation == null )
-                if ( ! Fuseki.GSP_DIRECT_NAMING ) 
-                    ServletOps.errorBadRequest(format("dataset=%s, service=%s", dataAccessPoint.getName(), endpointName));
-                else
-                    throw new InternalErrorException("Inconsistent: GSP_DIRECT_NAMING but no operation");
-        } else {
-            // Endpoint ""
-            operation = chooseOperation(action, dSrv);
-        }
-
-        // ---- Auth checking.
-        // -- Server-level authorization.
-        // Checking was carried out by servlet filter AuthFilter.
-        // Need to check Data service and endpoint authorization policies.
-        String user = action.getUser();
-        // -- Data service level authorization
-        if ( dSrv.authPolicy() != null ) {
-            if ( ! dSrv.authPolicy().isAllowed(user) )
-                ServletOps.errorForbidden();
-        }
-        
-        // -- Endpoint level authorization
-        // Make sure all contribute authentication.
-        if ( action.getEndpoint() != null ) {
-            // Specific endpoint chosen.
-            Auth.allow(user, action.getEndpoint().getAuthPolicy(), ServletOps::errorForbidden);
-        } else {
-            // No Endpoint name given; there may be several endpoints for the operation.
-            // authorization is the AND of all endpoints.
-            Collection<Endpoint> x = getEndpoints(dSrv, operation);
-            if ( operation != null && x.isEmpty() )
-                throw new InternalErrorException("Inconsistent: no endpoints for "+operation);
-            x.forEach(ep->{
-                Auth.allow(user, ep.getAuthPolicy(), ServletOps::errorForbidden);
-            });
-        }
-        // ---- End auth checking.
-        
-        // Decide the code to execute the request.
-        // For ServiceRouter, this involves a lookup in the service dispatch registry.
-        // For directly called services, they override the operations called by this.executeLifecycle. 
-        ActionService handler = selectProcessor.apply(action, operation);
-        if ( handler == null )
-            ServletOps.errorBadRequest(format("dataset=%s: op=%s", dataAccessPoint.getName(), operation.getName()));
-        handler.executeLifecycle(action);
-        return;
-    }
-
-    // Find the endpoints for an operation.
-    // This is GSP_R/GSP_RW and Quads_R/Quads_RW aware.
-    // If asked for GSP_R and there are no endpoints for GSP_R, try GSP_RW.
-    // Ditto Quads_R -> Quads_RW.
-    private Collection<Endpoint> getEndpoints(DataService dSrv, Operation operation) {
-        if ( operation == null )
-            return Collections.emptySet();
-        Collection<Endpoint> x = dSrv.getEndpoints(operation);
-        if ( x == null || x.isEmpty() ) {
-            if ( operation == Operation.GSP_R )
-                x = dSrv.getEndpoints(Operation.GSP_RW);
-            else if ( operation == Operation.Quads_R )
-                x = dSrv.getEndpoints(Operation.Quads_RW);
-        }
-        return x;
-    }
-    
-    /**
-     * Return the operation that corresponds to the endpoint name for a given data service.
-     * Side effect: This operation should set the selected endpoint in the HttpAction
-     * if this operation is determined to be a specific endpoint.
-     */
-    protected Operation chooseOperation(HttpAction action, DataService dataService, String endpointName) {
-        // Overridden by the ServiceRouter.
-        // This default implementation is plain service name to operation based on the
-        // DataService as would be used by operation servlets bound by web.xml
-        // except Fuseki can add and delete mapping while running.
-        Endpoint ep = dataService.getEndpoint(endpointName);
-        Operation operation = ep.getOperation();
-        action.setEndpoint(ep);
-        return operation;
-    }
-
-    /**
-     * Return the operation that corresponds to the request when there is no endpoint name. 
-     * This operation does not set the selected endpoint in the HttpAction.
-     */
-    protected Operation chooseOperation(HttpAction action, DataService dataService) {
-        // No default implementation for directly bound services operation servlets.
-        return null;
-    }
-
-    private void executeRequest(HttpAction action, ActionService servlet) {
-        if ( true ) {
-            // Execute an ActionService.
-            // Bypasses HttpServlet.service to doMethod dispatch.
-            servlet.executeLifecycle(action);
-            return;
-        }
-        if ( false ) {
-            // Execute by calling the whole servlet mechanism.
-            // This causes HttpServlet.service to call the appropriate doMethod.
-            // but the action, and the id, are not passed on and a ne one is created.
-            try {
-                servlet.service(action.request, action.response);
-            }
-            catch (ServletException | IOException e) {
-                ServletOps.errorOccurred(e);
-            }
-        }
-    }
-
-    /**
-     * Execute a SPARQL request. Statistics have not been adjusted at this point.
-     * 
-     * @param action
-     */
-    protected void executeAction(HttpAction action) {
-        executeLifecycle(action);
-    }
-
-    /**
-     * Standard execution lifecycle for a SPARQL Request.
-     * <ul>
-     * <li>{@link #startRequest(HttpAction)}</li>
-     * <li>initial statistics,</li>
-     * <li>{@link #validate(HttpAction)} request,</li>
-     * <li>{@link #perform(HttpAction)} request,</li>
-     * <li>completion/error statistics,</li>
-     * <li>{@link #finishRequest(HttpAction)}
-     * </ul>
-     * 
-     * @param action
-     */
-    // This is the service request lifecycle.
-    final protected void executeLifecycle(HttpAction action) {
+    protected void executeLifecycle(HttpAction action) {
         // And also HTTP counter
-        CounterSet csService = action.getDataService().getCounters();
+        
+        CounterSet csService = 
+            (action.getDataService() == null) ? null : action.getDataService().getCounters();
         CounterSet csOperation = null;
         if ( action.getEndpoint() != null )
             // Direct naming GSP does not have an "endpoint".
@@ -263,7 +55,7 @@ public abstract class ActionService extends ActionBase {
         }
 
         try {
-            perform(action);
+            execute(action);
             // Success
             incCounter(csOperation, RequestsGood);
             incCounter(csService, RequestsGood);
@@ -274,62 +66,4 @@ public abstract class ActionService extends ActionBase {
             throw ex;
         }
     }
-
-    /**
-     * Map request {@link HttpAction} to uri in the registry. A return of {@code null}
-     * means no mapping done (passthrough).
-     * 
-     * @param uri
-     *            the URI
-     * @return the dataset
-     */
-    protected String mapRequestToDataset(HttpAction action) {
-        return ActionLib.mapRequestToDataset(action);
-    }
-
-    /**
-     * Map request to uri in the registry. {@code null} means no mapping done
-     * (passthrough).
-     */
-    protected String mapRequestToOperation(HttpAction action, DataAccessPoint dataAccessPoint) {
-        return ActionLib.mapRequestToOperation(action, dataAccessPoint);
-    }
-
-    /** Increment counter */
-    protected static void incCounter(Counters counters, CounterName name) {
-        if ( counters == null )
-            return;
-        incCounter(counters.getCounters(), name);
-    }
-
-    /** Decrement counter */
-    protected static void decCounter(Counters counters, CounterName name) {
-        if ( counters == null )
-            return;
-        decCounter(counters.getCounters(), name);
-    }
-
... 24421 lines suppressed ...