Merge pull request #787 from strangepleasures/JENA-1950

JENA-1950 Replace Nashorn with GraalVM-based JavaScript engine
diff --git a/jena-arq/src-examples/arq/examples/riot/Ex_WriteJsonLD.java b/jena-arq/src-examples/arq/examples/riot/Ex_WriteJsonLD.java
index 39eaefb..d66f592 100644
--- a/jena-arq/src-examples/arq/examples/riot/Ex_WriteJsonLD.java
+++ b/jena-arq/src-examples/arq/examples/riot/Ex_WriteJsonLD.java
@@ -200,7 +200,8 @@
         DatasetGraph g = DatasetFactory.wrap(m).asDatasetGraph();
         JsonLDWriteContext ctx = new JsonLDWriteContext();
 
-        // The following should work, but unfortunately it doesn't (with JSONLD-java 0.8.3):
+        // The following should work for Uris returning JSON-LD,
+        // but unfortunately it doesn't for schema.org due to the following bug: https://github.com/jsonld-java/jsonld-java/issues/289:
         ctx.setJsonLDContext("\"http://schema.org/\"");
         System.out.println("\n--- Setting the context to a URI, WRONG WAY: it's slow, and the output is not JSON-LD. Sorry about that. ---");
         write(g, RDFFormat.JSONLD_COMPACT_PRETTY, ctx);
@@ -214,7 +215,6 @@
         // the output process must download the vocab before anything.
         // (that's why the previous attempt was slow)
         // -> that would not be an very efficient way to output your data.
-        // -> it doesn't work, (with JSONLD-java 0.8.3), but no regret.
 
         // To achieve the expected result,
         // you have to do 2 things:
diff --git a/jena-arq/src/main/java/org/apache/jena/query/QueryFactory.java b/jena-arq/src/main/java/org/apache/jena/query/QueryFactory.java
index 0e84941..aa3564b 100644
--- a/jena-arq/src/main/java/org/apache/jena/query/QueryFactory.java
+++ b/jena-arq/src/main/java/org/apache/jena/query/QueryFactory.java
@@ -18,9 +18,12 @@
 
 package org.apache.jena.query;
 
+import java.io.InputStream;
+
 import org.apache.jena.atlas.io.IO;
 import org.apache.jena.riot.system.IRIResolver ;
 import org.apache.jena.riot.system.stream.StreamManager;
+import org.apache.jena.shared.NotFoundException;
 import org.apache.jena.sparql.lang.ParserARQ ;
 import org.apache.jena.sparql.lang.SPARQLParser ;
 import org.apache.jena.sparql.lang.SPARQLParserRegistry ;
@@ -229,6 +232,9 @@
         if ( filemanager == null )
             filemanager = StreamManager.get() ;
 
+        InputStream in = filemanager.open(url);
+        if ( in == null )
+            throw new NotFoundException("Not found: "+url);
         String qStr = IO.readWholeFileAsUTF8(filemanager.open(url));
         if ( baseURI == null )
             baseURI = url ;
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/JsonLDReadContext.java b/jena-arq/src/main/java/org/apache/jena/riot/JsonLDReadContext.java
new file mode 100644
index 0000000..22ffb34
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/riot/JsonLDReadContext.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.riot;
+
+import com.github.jsonldjava.core.JsonLdOptions;
+import org.apache.jena.atlas.web.ContentType;
+import org.apache.jena.riot.lang.JsonLDReader;
+import org.apache.jena.riot.system.StreamRDF;
+import org.apache.jena.sparql.util.Context;
+
+import java.io.InputStream;
+
+/**
+ * Set of parameters that can be used to control the reading of JSON-LD.
+ *
+ * This class provides setters to define a "Context" suitable to be passed as 
+ * last argument to  {@link ReaderRIOT#read(InputStream, String, ContentType, StreamRDF, Context)}
+ * when the ReaderRIOT has been created with one of the JSON-LD RDFFormat variants (that is, when it is
+ * an instance of {@link JsonLDReader})
+ *
+ * Parameters that are actually useful are ''documentLoader'' and ''produceGeneralizedRdf''.
+ *
+ */
+public class JsonLDReadContext extends Context {
+    /**
+     * Set the value of the JSON-LD "@context" node, used when reading a jsonld (overriding the actual @context in the jsonld). "Compact" and "Flattened" JSON-LD outputs.
+     *
+     * @param jsonLdContext the value of the "@context" node (a JSON value). Note that the use of an URI to pass an external context is not supported (as of JSONLD-java API 0.8.3)
+     *
+     * @see #setJsonLDContext(Object)
+     */
+    public void setJsonLDContext(String jsonLdContext) {
+        set(JsonLDReader.JSONLD_CONTEXT, jsonLdContext);
+    }
+
+    /**
+     * Set the value of the JSON-LD "@context" node, used when reading a jsonld (overriding the actual @context in the jsonld). "Compact" and "Flattened" JSON-LD outputs.
+     *
+     *
+     * @param jsonLdContext the context as expected by JSON-LD java API. As of JSON-LD java 0.8.3, a Map
+     * defining the properties and the prefixes is OK. Note that the use an URI to pass an external context is not supported (JSONLD-java RDF 0.8.3)
+     *
+     * @see #setJsonLDContext(String)
+     */
+    public void setJsonLDContext(Object jsonLdContext) {
+        set(JsonLDReader.JSONLD_CONTEXT, jsonLdContext);
+    }
+    /**
+     * Set the JSON-LD java API's options
+     *
+     * If not set, a default value is used.
+     *
+     * @param opts the options as defined by the JSON-LD java API
+     */
+    public void setOptions(JsonLdOptions opts) {
+        set(JsonLDReader.JSONLD_OPTIONS, opts);
+    }
+}
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/JsonLDWriteContext.java b/jena-arq/src/main/java/org/apache/jena/riot/JsonLDWriteContext.java
index d2e0b29..f40cc5d 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/JsonLDWriteContext.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/JsonLDWriteContext.java
@@ -15,6 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.jena.riot;
 
 import java.io.OutputStream;
@@ -59,7 +60,7 @@
      * Only useful for "Compact" and "Flattened" JSON-LD outputs, and not required: if not set,
      * a value for the "@Context" node is computed, based on the content of the dataset and its prefix mappings.
      * 
-     * @param jsonLdContext the value of the "@context" node (a JSON value). Note that the use of an URI to pass an external context is not supported (as of JSONLD-java API 0.8.3)
+     * @param jsonLdContext the value of the "@context" node (a JSON value). Some remote contexts might not be resolved property.
      * @see #setJsonLDContextSubstitution(String) for a way to overcome this problem.
      * 
      * @see #setJsonLDContext(Object)
@@ -74,8 +75,7 @@
      * Only useful for "Compact" and "Flattened" JSON-LD outputs, and not required: if not set,
      * a value for the "@Context" node is computed, based on the content of the dataset and its prefix mappings.
      * 
-     * @param jsonLdContext the context as expected by JSON-LD java API. As of JSON-LD java 0.8.3, a Map 
-     * defining the properties and the prefixes is OK. Note that the use an URI to pass an external context is not supported (JSONLD-java RDF 0.8.3)
+     * @param jsonLdContext the context as expected by JSON-LD java API. Some remote contexts might not be resolved property.
      * @see #setJsonLDContextSubstitution(String) for a way to overcome this problem.
      * 
      * @see #setJsonLDContext(String)
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/RDFParserRegistry.java b/jena-arq/src/main/java/org/apache/jena/riot/RDFParserRegistry.java
index 1bff3b0..c703f76 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/RDFParserRegistry.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/RDFParserRegistry.java
@@ -31,9 +31,11 @@
 import org.apache.jena.atlas.lib.InternalErrorException ;
 import org.apache.jena.atlas.web.ContentType ;
 import org.apache.jena.riot.lang.* ;
+import org.apache.jena.riot.system.ErrorHandlerFactory;
 import org.apache.jena.riot.system.ParserProfile;
 import org.apache.jena.riot.system.StreamRDF;
 import org.apache.jena.riot.thrift.BinRDF ;
+import org.apache.jena.riot.thrift.RiotThriftException;
 import org.apache.jena.sparql.util.Context ;
 
 /** The registry of languages and parsers.
@@ -206,14 +208,25 @@
     private static class ReaderRIOTFactoryThrift implements ReaderRIOTFactory {
         @Override
         public ReaderRIOT create(Lang language, ParserProfile profile) {
-            return new ReaderRDFThrift() ;
+            return new ReaderRDFThrift(profile) ;
         }
     }
 
     private static class ReaderRDFThrift implements ReaderRIOT {
+        private final ParserProfile profile; 
+        public ReaderRDFThrift(ParserProfile profile) { this.profile = profile; }
+
         @Override
         public void read(InputStream in, String baseURI, ContentType ct, StreamRDF output, Context context) {
-            BinRDF.inputStreamToStream(in, output) ;
+            try {
+                BinRDF.inputStreamToStream(in, output);
+            } catch (RiotThriftException ex) {
+                if ( profile != null && profile.getErrorHandler() != null )
+                    profile.getErrorHandler().error(ex.getMessage(), -1, -1);
+                else
+                    ErrorHandlerFactory.errorHandlerStd.error(ex.getMessage(), -1 , -1);
+                throw ex;
+            }
         }
 
         @Override
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/RIOT.java b/jena-arq/src/main/java/org/apache/jena/riot/RIOT.java
index 6fcc439..39300ec 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/RIOT.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/RIOT.java
@@ -19,6 +19,7 @@
 package org.apache.jena.riot ;
 
 import org.apache.jena.query.ARQ ;
+import org.apache.jena.riot.lang.JsonLDReader;
 import org.apache.jena.riot.resultset.ResultSetLang;
 import org.apache.jena.sparql.SystemARQ ;
 import org.apache.jena.sparql.mgt.SystemInfo ;
@@ -109,12 +110,9 @@
 
     // ---- Symbols
 
-    /**
-     * Symbol to use to pass (in a Context object) the "@context" to be used when reading jsonld
-     * (overriding the actual @context in the jsonld)
-     * Expected value: the value of the "@context",
-     * as expected by the JSONLD-java API (a Map) */
-    public static final Symbol JSONLD_CONTEXT = Symbol.create("http://jena.apache.org/riot/jsonld#JSONLD_CONTEXT");
+    /** @deprecated Use {@link JsonLDReader#JSONLD_CONTEXT} */
+    @Deprecated
+    public static final Symbol JSONLD_CONTEXT = JsonLDReader.JSONLD_CONTEXT;
 
     private static String TURTLE_SYMBOL_BASE = "http://jena.apache.org/riot/turtle#";
 
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/lang/JsonLDReader.java b/jena-arq/src/main/java/org/apache/jena/riot/lang/JsonLDReader.java
index 07a216b..305fe1e 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/lang/JsonLDReader.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/lang/JsonLDReader.java
@@ -26,6 +26,7 @@
 import java.util.Map.Entry;
 import java.util.Objects;
 
+import com.fasterxml.jackson.core.JsonParseException;
 import org.apache.jena.atlas.io.IO ;
 import org.apache.jena.atlas.lib.InternalErrorException ;
 import org.apache.jena.atlas.web.ContentType ;
@@ -47,14 +48,42 @@
 import com.github.jsonldjava.core.JsonLdTripleCallback;
 import com.github.jsonldjava.core.RDFDataset;
 import com.github.jsonldjava.utils.JsonUtils ;
+import org.apache.jena.sparql.util.Symbol;
 
 /**
+ * One can pass a jsonld context using the (jena) Context mechanism, defining a (jena) Context
+ * (sorry for this clash of "contexts"), (cf. last argument in
+ * {@link ReaderRIOT#read(InputStream in, String baseURI, ContentType ct, StreamRDF output, Context context)})
+ * with:
+ * <pre>
+ * Context jenaContext = new Context()
+ * jenaCtx.set(JsonLdReader.JSONLD_CONTEXT, contextAsJsonString);
+ * </pre>
+ * where contextAsJsonString is a JSON string containing the value of the "@context".
+ *
+ * It is also possible to define the different options supported
+ * by JSONLD-java using the {@link #JSONLD_OPTIONS} Symbol
+ *
+ * The {@link JsonLDReadContext} is a convenience class that extends Context and
+ * provides methods to set the values of these different Symbols that are used in controlling the writing of JSON-LD.
  * Note: it is possible to override jsonld's "@context" value by providing one,
- * using a {@link org.apache.jena.sparql.util.Context}, and setting the {@link RIOT#JSONLD_CONTEXT} Symbol's value
+ * using a {@link org.apache.jena.sparql.util.Context}, and setting the {@link JsonLDReader#JSONLD_CONTEXT} Symbol's value
  * to the data expected by JSON-LD java API (a {@link Map}).
  */
 public class JsonLDReader implements ReaderRIOT
 {
+    private static final String SYMBOLS_NS = "http://jena.apache.org/riot/jsonld#" ;
+    private static Symbol createSymbol(String localName) {
+        return Symbol.create(SYMBOLS_NS + localName);
+    }
+    /**
+     * Symbol to use to pass (in a Context object) the "@context" to be used when reading jsonld
+     * (overriding the actual @context in the jsonld)
+     * Expected value: the value of the "@context",
+     * as expected by the JSONLD-java API (a Map) */
+    public static final Symbol JSONLD_CONTEXT = createSymbol("JSONLD_CONTEXT");
+    /** value: the option object expected by JsonLdProcessor (instance of JsonLdOptions) */
+    public static final Symbol JSONLD_OPTIONS = createSymbol("JSONLD_OPTIONS");
     private /*final*/ ErrorHandler errorHandler = ErrorHandlerFactory.getDefaultErrorHandler() ;
     private /*final*/ ParserProfile profile;
     
@@ -67,7 +96,26 @@
     public void read(Reader reader, String baseURI, ContentType ct, StreamRDF output, Context context) {
         try {
             Object jsonObject = JsonUtils.fromReader(reader) ;
-            read$(jsonObject, baseURI, ct, output, context) ;
+            readWithJsonLDCtxOptions(jsonObject, baseURI, output, context) ;
+        }
+        catch (JsonProcessingException ex) {    
+            // includes JsonParseException
+            // The Jackson JSON parser, or addition JSON-level check, throws up something.
+            JsonLocation loc = ex.getLocation() ;
+            errorHandler.error(ex.getOriginalMessage(), loc.getLineNr(), loc.getColumnNr()); 
+            throw new RiotException(ex.getOriginalMessage()) ;
+        }
+        catch (IOException e) {
+            errorHandler.error(e.getMessage(), -1, -1); 
+            IO.exception(e) ;
+        }
+    }
+
+    @Override
+    public void read(InputStream in, String baseURI, ContentType ct, StreamRDF output, Context context) {
+        try {
+            Object jsonObject = JsonUtils.fromInputStream(in) ;
+            readWithJsonLDCtxOptions(jsonObject, baseURI, output, context) ;
         }
         catch (JsonProcessingException ex) {    
             // includes JsonParseException
@@ -83,37 +131,20 @@
     }
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Override
-    public void read(InputStream in, String baseURI, ContentType ct, StreamRDF output, Context context) {
-        try {
-            Object jsonObject = JsonUtils.fromInputStream(in) ;
-            
-            if (context != null) {
-                Object jsonldCtx = context.get(RIOT.JSONLD_CONTEXT);
-                if (jsonldCtx != null) {
-                    if (jsonObject instanceof Map) {
-                        ((Map) jsonObject).put("@context", jsonldCtx);
-                    } else {
-                        errorHandler.warning("Unexpected: not a Map; unable to set JsonLD's @context",-1,-1);
-                    }
-                }
+    private void readWithJsonLDCtxOptions(Object jsonObject, String baseURI, final StreamRDF output, Context context)  throws JsonParseException, IOException {
+        JsonLdOptions options = getJsonLdOptions(baseURI, context) ;
+        Object jsonldCtx = getJsonLdContext(context);
+        if (jsonldCtx != null) {
+            if (jsonObject instanceof Map) {
+                ((Map) jsonObject).put("@context", jsonldCtx);
+            } else {
+                errorHandler.warning("Unexpected: not a Map; unable to set JsonLD's @context",-1,-1);
             }
-            read$(jsonObject, baseURI, ct, output, context) ;
         }
-        catch (JsonProcessingException ex) {    
-            // includes JsonParseException
-            // The Jackson JSON parser, or addition JSON-level check, throws up something.
-            JsonLocation loc = ex.getLocation() ;
-            errorHandler.error(ex.getOriginalMessage(), loc.getLineNr(), loc.getColumnNr()); 
-            throw new RiotException(ex.getOriginalMessage()) ;
-        }
-        catch (IOException e) {
-            errorHandler.error(e.getMessage(), -1, -1); 
-            IO.exception(e) ;
-        }
+        read$(jsonObject, options, output);
     }
-    
-    private void read$(Object jsonObject, String baseURI, ContentType ct, final StreamRDF output, Context context) {
+
+    private void read$(Object jsonObject, JsonLdOptions options, final StreamRDF output) {
         output.start() ;
         try {       	
             JsonLdTripleCallback callback = new JsonLdTripleCallback() {
@@ -154,8 +185,6 @@
                     return null ;
                 }
             } ;
-            JsonLdOptions options = new JsonLdOptions(baseURI);
-            options.useNamespaces = true;
             JsonLdProcessor.toRDF(jsonObject, callback, options) ;
         }
         catch (JsonLdError e) {
@@ -165,6 +194,44 @@
         output.finish() ;
     }
 
+    /** Get the (jsonld) options from the jena context if exists or create default */
+    static private JsonLdOptions getJsonLdOptions(String baseURI, Context jenaContext) {
+        JsonLdOptions opts = null;
+        if (jenaContext != null) {
+            opts = (JsonLdOptions) jenaContext.get(JSONLD_OPTIONS);
+        }
+        if (opts == null) {
+            opts = defaultJsonLdOptions(baseURI);
+        }
+        return opts;
+    }
+
+    static private JsonLdOptions defaultJsonLdOptions(String baseURI) {
+        JsonLdOptions opts = new JsonLdOptions(baseURI);
+        opts.useNamespaces = true ; // this is NOT jsonld-java's default
+        return opts;
+    }
+
+    /** Get the (jsonld) context from the jena context if exists */
+    static private Object getJsonLdContext(Context jenaContext) throws JsonParseException, IOException {
+        Object ctx = null;
+        boolean isCtxDefined = false; // to allow jenaContext to set ctx to null. Useful?
+
+        if (jenaContext != null) {
+            if (jenaContext.isDefined(JSONLD_CONTEXT)) {
+                Object o = jenaContext.get(JSONLD_CONTEXT);
+                if (o != null) {
+                    if (o instanceof String) { // supposed to be a json string
+                        String jsonString = (String) o;
+                        o = JsonUtils.fromString(jsonString);
+                    }
+                    ctx = o;
+                }
+            }
+        }
+        return ctx;
+    }
+
     public static String LITERAL    = "literal" ;
     public static String BLANK_NODE = "blank node" ;
     public static String IRI        = "IRI" ;
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/JSONResultsKW.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/JSONResultsKW.java
index 2dbc9b6..aae25d1 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/JSONResultsKW.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/JSONResultsKW.java
@@ -45,6 +45,8 @@
 
     // RDF* Triple terms
     public static String kTriple        = "triple" ;
+    // Alternative type for RDF* triple terms.
+    public static String kStatement     = "statement" ;
     public static String kSubject       = "subject" ;
     public static String kPredicate     = "predicate" ;
     public static String kProperty      = "property" ;
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderJSON.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderJSON.java
index 95cc399..e614a4f 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderJSON.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultSetReaderJSON.java
@@ -199,7 +199,7 @@
 
             String type = stringOrNull(term, kType);
             
-            if ( kTriple.equals(type) ) {
+            if ( kTriple.equals(type) || kStatement.equals(type) ) {
                 JsonObject x = term.get(kValue).getAsObject();
                 return parseTripleTerm(x, labelMap);
             }
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsStAX.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsStAX.java
index 6407f53..2471716 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsStAX.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/ResultsStAX.java
@@ -388,7 +388,7 @@
                         varName = parser.getAttributeValue(null, XMLResults.dfAttrVarName) ;
                         break ;
                     }
-                    
+
                     Node value = parseOneTerm(tag);
                     if ( value != null ) {
                         if ( varName == null )
@@ -440,14 +440,14 @@
       if ( isTag(tag, XMLResults.dfUnbound) ) {
           return null;
       }
-      
+
       if ( isTag(tag, XMLResults.dfURI) ) {
           String uri = parser.getElementText() ;
           Node node = NodeFactory.createURI(uri) ;
           return node;
       }
-      
-      if ( isTag(tag, XMLResults.dfTriple) ) {
+
+      if ( isTag(tag, XMLResults.dfTriple) || isTag(tag, XMLResults.dfStatement) ) {
           // <triple>
           Node s = null;
           Node p = null;
@@ -458,30 +458,30 @@
               if ( event2 == XMLStreamConstants.END_ELEMENT ) {
                   // Early end.
                   String tagx = parser.getLocalName() ;
-                  if ( tagx.equals(XMLResults.dfTriple) )
+                  if ( tagx.equals(XMLResults.dfTriple) || tagx.equals(XMLResults.dfStatement) )
                       staxError("Incomplete triple term");
                   else
                       staxError("Mismatched tag: "+tagx);
               }
               //XMLStreamConstants.START_ELEMENT
-                  
+
               String tag2 = parser.getLocalName() ;
               // <subject> <property> <object>
               // One of subject, property, object (s,p,o)
-              
+
               if ( ! isOneOf(tag2, XMLResults.dfSubject, XMLResults.dfProperty, XMLResults.dfPredicate, XMLResults.dfObject,
                                    XMLResults.dfSubjectAlt, XMLResults.dfPropertyAlt, XMLResults.dfObjectAlt) )
                   staxError("Unexpected tag in triple term: "+tag2);
-              
+
               int event3 = parser.nextTag() ;
               String tag3 = parser.getLocalName() ;
               Node x = parseOneTerm(tag3);
-              
+
               // Read end </subject> etc.
               parser.nextTag() ;
 
               // Check for double assignment.
-              if ( isOneOf(tag2, XMLResults.dfSubject, XMLResults.dfSubjectAlt) ) 
+              if ( isOneOf(tag2, XMLResults.dfSubject, XMLResults.dfSubjectAlt) )
                   s = x ;
               else if ( isOneOf(tag2, XMLResults.dfProperty, XMLResults.dfPredicate, XMLResults.dfPropertyAlt) )
                   p = x ;
@@ -493,13 +493,13 @@
                   if ( event4 == XMLStreamConstants.START_ELEMENT )
                       staxError("Too many terms for a triple");
                   // XMLStreamConstants.END_ELEMENT )
-                  if ( ! tagx.equals(XMLResults.dfTriple) )
+                  if ( ! tagx.equals(XMLResults.dfTriple) && ! tagx.equals(XMLResults.dfStatement) )
                       staxError("Expecting </triple>: "+tagx);
                   // </triple>
                   break;
               }
           }
-          
+
           if ( s == null || p == null || o == null )
               staxError("Bad <triple> term");
           Node node = NodeFactory.createTripleNode(s, p, o);
@@ -515,7 +515,7 @@
         }
         return false;
     }
-    
+
     static protected void addBinding(BindingMap binding, Var var, Node value) {
         Node n = binding.get(var);
         if ( n != null ) {
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/XMLResults.java b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/XMLResults.java
index ff7565f..0fd4c4d 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/XMLResults.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/resultset/rw/XMLResults.java
@@ -27,10 +27,10 @@
 
     public static final String baseNamespace   = ARQConstants.srxPrefix ;
     public static final String xsBaseURI       = ARQConstants.XML_SCHEMA_NS ;
-    
+
     public static final String dfAttrVarName   = "name" ;
     public static final String dfAttrDatatype  = "datatype" ;
-    
+
     public static final String dfNamespace  = baseNamespace ;
     public static final String dfRootTag    = "sparql" ;
     public static final String dfHead       = "head" ;
@@ -39,11 +39,12 @@
     public static final String dfResults    = "results" ;
     public static final String dfSolution   = "result" ;
     public static final String dfBinding    = "binding" ;
-    
+
     public static final String dfBNode      = "bnode" ;
     public static final String dfURI        = "uri" ;
     public static final String dfLiteral    = "literal" ;
     public static final String dfTriple     = "triple" ;
+    public static final String dfStatement  = "statement" ;
     public static final String dfSubject    = "subject" ;
     public static final String dfProperty   = "property" ;
     public static final String dfObject     = "object" ;
@@ -53,7 +54,7 @@
     public static final String dfSubjectAlt  = "s" ;
     public static final String dfPropertyAlt = "p" ;
     public static final String dfObjectAlt   = "o" ;
-    
+
     public static final String dfUnbound    = "unbound" ;
 
     public static final String dfBoolean    = "boolean" ;
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/thrift/BinRDF.java b/jena-arq/src/main/java/org/apache/jena/riot/thrift/BinRDF.java
index 52e6e92..47561af 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/thrift/BinRDF.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/thrift/BinRDF.java
@@ -157,8 +157,8 @@
         final Thrift2StreamRDF s = new Thrift2StreamRDF(pmap, dest) ;
         dest.start() ;
         apply(protocol, z -> TRDF.visit(z, s)) ;
+        // Includes flushing the protocol.
         dest.finish() ;
-        // No need to flush - we read from the protocol ; 
     }
 
     /**
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/thrift/Thrift2StreamRDF.java b/jena-arq/src/main/java/org/apache/jena/riot/thrift/Thrift2StreamRDF.java
index 3186bfc..30aa0b3 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/thrift/Thrift2StreamRDF.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/thrift/Thrift2StreamRDF.java
@@ -19,7 +19,6 @@
 package org.apache.jena.riot.thrift;
 
 import org.apache.jena.graph.Triple ;
-import org.apache.jena.riot.RiotException ;
 import org.apache.jena.riot.system.PrefixMap ;
 import org.apache.jena.riot.system.StreamRDF ;
 import org.apache.jena.riot.thrift.wire.RDF_PrefixDecl ;
@@ -58,10 +57,7 @@
     public void visit(RDF_PrefixDecl prefixDecl) {
         String prefix = prefixDecl.getPrefix() ;
         String iriStr = prefixDecl.getUri() ;
-        try {
-            pmap.add(prefix, iriStr) ; 
-        } catch (RiotException ex) {}
+        pmap.add(prefix, iriStr) ;
         dest.prefix(prefix, iriStr) ;
     }
-
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLDWriter.java b/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLDWriter.java
index 3a70dfb..6a6f888 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLDWriter.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLDWriter.java
@@ -193,7 +193,6 @@
         JsonLdOptions opts = getJsonLdOptions(baseURI, jenaContext) ;
 
         // we can benefit from the fact we know that there are no duplicates in the jsonld RDFDataset that we create
-        // (optimization in jsonld-java 0.8.3)
         // see https://github.com/jsonld-java/jsonld-java/pull/173
 
         // with this, we cannot call the json-ld fromRDF method that assumes no duplicates in RDFDataset
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java
index 32728ac..89e18ad 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/Algebra.java
@@ -50,10 +50,10 @@
 public class Algebra
 {
     // -------- Optimize
-    
+
     /** Apply static transformations to a query to optimize it */
     public static Op optimize(Op op) { return optimize(op, null) ; }
-    
+
     /** Apply static transformations to a query to optimize it */
     public static Op optimize(Op op, Context context)
     {
@@ -63,10 +63,10 @@
         if ( op == null )
             return null ;
         return Optimize.optimize(op, context) ;
-    }   
-    
+    }
+
     // -------- Compile
-    
+
     /** Compile a query - pattern and modifiers.  */
     public static Op compile(Query query)
     {
@@ -88,21 +88,25 @@
     {
         return AlgebraQuad.quadize(op) ;
     }
-    
+
     /** Turn an algebra expression into quadblock form */
     public static Op toQuadBlockForm(Op op)
     {
         return AlgebraQuad.quadizeBlock(op) ;
     }
-    
-    /** Transform an algebra expression so that default graph is union of the named graphs. */
+
+    /** Transform an algebra expression so that default graph is union of the named graphs.
+     * Does not work with property paths.
+     * @deprecated To be removed
+     */
+    @Deprecated
     public static Op unionDefaultGraph(Op op)
     {
         return TransformUnionQuery.transform(op) ;
     }
-    
+
     // -------- SSE uses these operations ...
-    
+
     static public Op read(String filename)
     {
         Item item = SSE.readFile(filename) ;
@@ -114,20 +118,20 @@
         Item item = SSE.parse(string) ;
         return parse(item) ;
     }
-    
+
 
     static public Op parse(String string, PrefixMapping pmap)
     {
         Item item = SSE.parse(string, pmap) ;
         return parse(item) ;
     }
-    
+
     static public Op parse(Item item)
     {
         Op op = BuilderOp.build(item) ;
         return op ;
     }
-    
+
     // -------- Execute
 
     static public QueryIterator exec(Op op, Dataset ds)
@@ -174,16 +178,16 @@
         QueryEngineRef qe = new QueryEngineRef(op, dsg, ARQ.getContext().copy()) ;
         return qe.getPlan().iterator() ;
     }
-    
-    // This is the SPARQL merge rule. 
+
+    // This is the SPARQL merge rule.
     public static Binding merge(Binding bindingLeft, Binding bindingRight)
     {
         // Test to see if compatible: Iterate over variables in left
         boolean matches = compatible(bindingLeft, bindingRight) ;
-        
-        if ( ! matches ) 
+
+        if ( ! matches )
             return null ;
-        
+
         // If compatible, merge. Iterate over variables in right but not in left.
         BindingMap b = BindingFactory.create(bindingLeft) ;
         for ( Iterator<Var> vIter = bindingRight.vars() ; vIter.hasNext() ; )
@@ -195,28 +199,28 @@
         }
         return b ;
     }
-    
+
     public static boolean compatible(Binding bindingLeft, Binding bindingRight)
     {
         // Test to see if compatible: Iterate over variables in left
         for ( Iterator<Var> vIter = bindingLeft.vars() ; vIter.hasNext() ; )
         {
             Var v = vIter.next();
-            Node nLeft  = bindingLeft.get(v) ; 
+            Node nLeft  = bindingLeft.get(v) ;
             Node nRight = bindingRight.get(v) ;
-            
+
             if ( nRight != null && ! nRight.equals(nLeft) )
                 return false ;
         }
         return true ;
     }
-    
+
     public static boolean disjoint(Binding binding1, Binding binding2)
     {
         Iterator<Var> iterVar1 = binding1.vars() ;
         for ( ; iterVar1.hasNext() ; )
         {
-            Var v = iterVar1.next() ; 
+            Var v = iterVar1.next() ;
             if ( binding2.contains(v) )
                 return false ;
         }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphOne.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphOne.java
index f7d5b77..1323c0a 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphOne.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphOne.java
@@ -35,9 +35,9 @@
 
 /** DatasetGraph of a single graph as default graph.
  * <p>
- *  Fixed as one graph (the default) - named graphs can notbe added nor the default graph changed, only the contents modified. 
+ *  Fixed as one graph (the default) - named graphs can notbe added nor the default graph changed, only the contents modified.
  *  <p>
- *  Ths dataset passes transactions down to a nominated backing {@link DatasetGraph}
+ *  This dataset passes transactions down to a nominated backing {@link DatasetGraph}.
  *  <p>
  *  It is particular suitable for use with an interference graph.
  */
@@ -58,7 +58,7 @@
         // Didn't find a GraphView so no backing DatasetGraph; work on the graph as given.
         return new DatasetGraphOne(graph);
     }
-    
+
     private static Graph unwrap(Graph graph) {
         for (;;) {
             if ( graph instanceof InfGraph ) {
@@ -71,26 +71,26 @@
             graph = graph2;
         }
     }
-    
+
     private DatasetGraphOne(Graph graph, DatasetGraph backing) {
         this.graph = graph;
         backingDGS = backing;
         supportsAbort = backing.supportsTransactionAbort();
         txn = backing;
     }
-    
+
     private DatasetGraphOne(Graph graph) {
-        // Not GraphView which was handled in create(Graph). 
+        // Not GraphView which was handled in create(Graph).
         this.graph = graph;
         txn = new TxnDataset2Graph(graph);
         //txn = TransactionalLock.createMRSW();
         backingDGS = null;
         // Don't advertise the fact but TxnDataset2Graph tries to provide abort.
-        // We can not guarantee it though because a plain, non-TIM, 
+        // We can not guarantee it though because a plain, non-TIM,
         // memory graph does not support abort.
         supportsAbort = false;
     }
-    
+
     @Override public void begin(TxnType txnType)        { txn.begin(txnType); }
     @Override public void begin(ReadWrite mode)         { txn.begin(mode); }
     @Override public void commit()                      { txn.commit(); }
@@ -102,7 +102,7 @@
     @Override public TxnType transactionType()          { return txn.transactionType(); }
     @Override public boolean supportsTransactions()     { return true; }
     @Override public boolean supportsTransactionAbort() { return supportsAbort; }
-    
+
     @Override
     public boolean containsGraph(Node graphNode) {
         if ( isDefaultGraph(graphNode) )
@@ -114,7 +114,7 @@
     public Graph getDefaultGraph() {
         return graph;
     }
-    
+
     @Override
     public Graph getUnionGraph() {
         return GraphZero.instance();
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/AssemblerUtils.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/AssemblerUtils.java
index 6c46af9..d63b279 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/AssemblerUtils.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/AssemblerUtils.java
@@ -41,32 +41,34 @@
 
 public class AssemblerUtils
 {
-    // Wrappers for reading things form a file - assumes one of the thing per file. 
+    // Wrappers for reading things form a file - assumes one of the thing per file.
     public static PrefixMapping readPrefixMapping(String file)
     {
         PrefixMapping pm = (PrefixMapping)AssemblerUtils.build(file, JA.PrefixMapping) ;
         return pm ;
     }
-    
-    private static boolean initialized = false ; 
-    
-    static { JenaSystem.init() ; } 
-    
+
+    private static boolean initialized = false ;
+
+    static { JenaSystem.init() ; }
+
     static public void init()
     {
         if ( initialized )
             return ;
         initialized = true ;
-        registerDataset(tDataset,         new DatasetAssembler()) ;
-        registerDataset(tDatasetOne,      new DatasetOneAssembler()) ;
-        registerDataset(tDatasetZero,     new DatasetNullAssembler(tDatasetZero)) ;
-        registerDataset(tDatasetSink,     new DatasetNullAssembler(tDatasetSink)) ;
-        registerDataset(tMemoryDataset,   new InMemDatasetAssembler()) ;
-        registerDataset(tDatasetTxnMem,   new InMemDatasetAssembler()) ;
+        registerDataset(tDataset,         new DatasetAssembler());
+        registerDataset(tDatasetOne,      new DatasetOneAssembler());
+        registerDataset(tDatasetZero,     new DatasetNullAssembler(tDatasetZero));
+        registerDataset(tDatasetSink,     new DatasetNullAssembler(tDatasetSink));
+        registerDataset(tMemoryDataset,   new InMemDatasetAssembler());
+        registerDataset(tDatasetTxnMem,   new InMemDatasetAssembler());
+
+        registerModel(tViewGraph,          new ViewGraphAssembler());
     }
-    
+
     private static Model modelExtras = ModelFactory.createDefaultModel() ;
-    
+
     /** Register an assembler that creates a dataset */
     static public void registerDataset(Resource r, Assembler a) {
         register(ConstAssembler.general(), r, a, DatasetAssembler.getType()) ;
@@ -77,7 +79,7 @@
         register(ConstAssembler.general(), r, a, JA.Model) ;
     }
 
-    /** Register an addition assembler */  
+    /** Register an additional assembler */
     static public void register(AssemblerGroup g, Resource r, Assembler a, Resource superType) {
         registerAssembler(g, r, a) ;
         if ( superType != null && ! superType.equals(r) ) {
@@ -86,8 +88,8 @@
            modelExtras.add(r, RDFS.Init.subClassOf(), superType) ;
         }
     }
-    
-    /** register */ 
+
+    /** register */
     public static void registerAssembler(AssemblerGroup group, Resource r, Assembler a) {
         if ( group == null )
             group = ConstAssembler.general();
@@ -104,7 +106,7 @@
         addRegistered(spec);
         return spec ;
     }
-    
+
     /** Add any extra information to the model.
      * Such information includes registration of datasets (e.g. TDB1, TDB2)
      * done by {@link #register} ({@link #registerDataset}, {@link #registerModel}.
@@ -116,36 +118,36 @@
         model.add(modelExtras) ;
         return model ;
     }
-    
+
     public static Object build(String assemblerFile, String typeURI) {
         Resource type = ResourceFactory.createResource(typeURI) ;
-        return build(assemblerFile, type) ; 
+        return build(assemblerFile, type) ;
     }
-    
+
     public static Object build(String assemblerFile, Resource type) {
         if ( assemblerFile == null )
             throw new ARQException("No assembler file") ;
         Model spec = readAssemblerFile(assemblerFile) ;
-        
+
         Resource root = null ;
         try {
             root = GraphUtils.findRootByType(spec, type) ;
             if ( root == null )
                 throw new ARQException("No such type: <"+type+">");
-            
+
         } catch (TypeNotUniqueException ex)
         { throw new ARQException("Multiple types for: "+tDataset) ; }
         return Assembler.general.open(root) ;
     }
-    
-    /** Look for and build context declarations. 
+
+    /** Look for and build context declarations.
      * e.g.
      * <pre>
      * root ... ;
      *   ja:context [ ja:cxtName "arq:queryTimeout" ;  ja:cxtValue "10000" ] ;
      *   ...
      * </pre>
-     * Short name forms of context parameters can be used.  
+     * Short name forms of context parameters can be used.
      * Setting as string "undef" will remove the context setting.
      * Returns null when there is no {@link JA#context} on the resource.
      */
@@ -157,20 +159,20 @@
         mergeContext(r, context);
         return context;
     }
-        
+
     /** @deprecated Use {@link #mergeContext(Resource, Context)} */
     public static void setContext(Resource r, Context context) {
         mergeContext(r, context);
     }
-    
-    /** Look for and merge in context declarations. 
+
+    /** Look for and merge in context declarations.
      * e.g.
      * <pre>
      * root ... ;
      *   ja:context [ ja:cxtName "arq:queryTimeout" ;  ja:cxtValue "10000" ] ;
      *   ...
      * </pre>
-     * Short name forms of context parameters can be used.  
+     * Short name forms of context parameters can be used.
      * Setting as string "undef" will remove the context setting.
      */
     public static void mergeContext(Resource r, Context context) {
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/DatasetAssemblerVocab.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/DatasetAssemblerVocab.java
index d55d7d1..0bdd0ed 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/DatasetAssemblerVocab.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/DatasetAssemblerVocab.java
@@ -27,7 +27,7 @@
 {
     public static final String NS = JA.getURI() ;
     public static String getURI() { return NS ; }
-    
+
     // General dataset
     public static final Resource tDataset            = ResourceFactory.createResource(NS+"RDFDataset") ;
     // Dataset to hold exactly one model.
@@ -35,23 +35,27 @@
 
     // In-memory dataset
     public static final Resource tDatasetTxnMem      = ResourceFactory.createResource(NS+"DatasetTxnMem") ;
-    
+
     // Specialised datasets
     public static final Resource tMemoryDataset      = ResourceFactory.createResource(NS+"MemoryDataset") ;
     public static final Resource tDatasetZero        = ResourceFactory.createResource(NS+"RDFDatasetZero") ;
     public static final Resource tDatasetSink        = ResourceFactory.createResource(NS+"RDFDatasetSink") ;
-    
+    public static final Resource tViewGraph          = ResourceFactory.createResource(NS+"ViewGraph") ;
+
+    public static final Property pDataset            = ResourceFactory.createProperty(NS, "dataset") ;
+
     public static final Property pDefaultGraph       = ResourceFactory.createProperty(NS, "defaultGraph") ;
+
     public static final Property pNamedGraph         = ResourceFactory.createProperty(NS, "namedGraph") ;
-    
     public static final Property pGraphName          = ResourceFactory.createProperty(NS, "graphName") ;
+
     public static final Property pGraph              = ResourceFactory.createProperty(NS, "graph") ;
     public static final Property pGraphAlt           = ResourceFactory.createProperty(NS, "graphData") ;
 
     public static final Property pIndex              = ResourceFactory.createProperty(NS, "textIndex") ;
-    
+
     public static final Property pTransactional      = ResourceFactory.createProperty(NS, "transactional") ;
-    
+
     public static final Property pContext            = ResourceFactory.createProperty(NS, "context") ;
     public static final Property pCxtName            = ResourceFactory.createProperty(NS, "cxtName") ;
     public static final Property pCxtValue           = ResourceFactory.createProperty(NS, "cxtValue") ;
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/ViewGraphAssembler.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/ViewGraphAssembler.java
new file mode 100644
index 0000000..56af65c
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/ViewGraphAssembler.java
@@ -0,0 +1,68 @@
+/*
+ * 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.sparql.core.assembler;
+
+import static org.apache.jena.sparql.core.assembler.DatasetAssemblerVocab.pDataset;
+import static org.apache.jena.sparql.core.assembler.DatasetAssemblerVocab.pGraphName;
+import static org.apache.jena.sparql.core.assembler.DatasetAssemblerVocab.pNamedGraph;
+import static org.apache.jena.sparql.util.graph.GraphUtils.getAsStringValue;
+import static org.apache.jena.sparql.util.graph.GraphUtils.getResourceValue;
+
+import org.apache.jena.assembler.Assembler;
+import org.apache.jena.assembler.Mode;
+import org.apache.jena.assembler.assemblers.AssemblerBase;
+import org.apache.jena.assembler.exceptions.AssemblerException;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.DatasetFactory;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Resource;
+
+/** Assembler to project a model out of a dataset.
+ *
+ * <pre>
+ * &lt;#graph&gt; rdf:type ja:ViewGraph ;
+ *     ja:graphName "..." ;            # Optional: else default graph
+ *     js:dataset <#dataset> ;
+ *     .
+ * <#dataset> rdf:type ja:RDFDataset ; # Any subtype
+ *    ...
+ * </pre>
+ *
+ */
+public class ViewGraphAssembler extends AssemblerBase implements Assembler {
+
+    @Override
+    public Model open(Assembler a, Resource root, Mode mode)
+    {
+        Resource dataset = getResourceValue(root, pDataset) ;
+        if ( dataset == null )
+            throw new AssemblerException(root, "Must give a dataset with ja:dataset") ;
+
+        String graphName = null ;
+        if ( root.hasProperty(pNamedGraph) )
+            graphName = getAsStringValue(root, pNamedGraph) ;
+        if ( root.hasProperty(pGraphName) )
+            graphName = getAsStringValue(root, pGraphName) ;
+
+        Dataset ds = DatasetFactory.assemble(dataset);
+        Model model = (graphName == null) ? ds.getDefaultModel() : ds.getNamedModel(graphName) ;
+        return model;
+    }
+}
+
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/RX.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/RX.java
index 9e6184f..197fb56 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/RX.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/iterator/RX.java
@@ -72,7 +72,7 @@
         VarAlloc varAlloc = VarAlloc.get(context, ARQConstants.sysVarAllocRDFStar);
         if ( varAlloc == null ) {
             varAlloc = new VarAlloc(ARQConstants.allocVarTripleTerm);
-            context.set(ARQConstants.sysVarAllocRDFStar, varAlloc);  
+            context.set(ARQConstants.sysVarAllocRDFStar, varAlloc);
         }
         return varAlloc;
     }
@@ -118,14 +118,14 @@
         Node o1 = null;
 
         // Recurse.
-        if ( s.isNodeTriple() && ! s.isConcrete() ) {
+        if ( s.isNodeTriple() ) {
             Triple t2 = triple(s);
             Var var = varAlloc(execCxt).allocVar();
             Triple tripleTerm = Triple.create(t2.getSubject(), t2.getPredicate(), t2.getObject());
             chain = matchTripleStar(chain, var, tripleTerm, execCxt);
             s1 = var;
         }
-        if ( o.isNodeTriple() && ! o.isConcrete() ) {
+        if ( o.isNodeTriple() ) {
             Triple t2 = triple(o);
             Var var = varAlloc(execCxt).allocVar();
             Triple tripleTerm = Triple.create(t2.getSubject(), t2.getPredicate(), t2.getObject());
@@ -134,8 +134,8 @@
         }
 
         // Because of the test in rdfStarTriple,
-        // This code only happens when there is a a triple term. 
-        
+        // This code only happens when there is a a triple term.
+
         // No triple term in this triple.
         if ( s1 == null && o1 == null )
             return Pair.create(chain, patternTriple);
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/EnvJavaScript.java b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/EnvJavaScript.java
index 259ba83..3d1bfe3 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/function/js/EnvJavaScript.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/function/js/EnvJavaScript.java
@@ -52,7 +52,7 @@
         return new EnvJavaScript(context);
     }
     
-    private static EnvJavaScript global = null;
+    private static volatile EnvJavaScript global = null;
     
     /**
      * Return the global {@code EnvJavaScript}. 
@@ -61,9 +61,11 @@
     public static EnvJavaScript get() {
         if ( global == null ) {
             synchronized(EnvJavaScript.class) {
-                Context context = ARQ.getContext();
-                if ( context.isDefined(symJavaScriptFunctions) || context.isDefined(symJavaScriptLibFile) )
-                    global = create(ARQ.getContext());
+                if ( global == null ) {
+                    Context context = ARQ.getContext();
+                    if ( context.isDefined(symJavaScriptFunctions) || context.isDefined(symJavaScriptLibFile) )
+                        global = create(context);
+                }
             }
         }
         return global ;
@@ -90,7 +92,7 @@
     // For now, in combination with the implementation of JSEngine,
     // we keep separate Nashorn script engines. 
     
-    private Pool<JSEngine> pool = PoolSync.create(new PoolBase<JSEngine>());
+    private final Pool<JSEngine> pool = PoolSync.create(new PoolBase<JSEngine>());
     
     private EnvJavaScript(Context context) {
         this.scriptLib = context.getAsString(symJavaScriptFunctions);
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/graph/PrefixMappingMem.java b/jena-arq/src/main/java/org/apache/jena/sparql/graph/PrefixMappingMem.java
index 80c927a..67908b1 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/graph/PrefixMappingMem.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/graph/PrefixMappingMem.java
@@ -50,7 +50,7 @@
     /** See notes on reverse mappings in {@link PrefixMappingBase}.
      * This is a complete implementation.
      * <p>
-     * Test {@lcode AbstractTestPrefixMapping.testSecondPrefixDeletedUncoversPreviousMap}.
+     * Test {@code AbstractTestPrefixMapping.testSecondPrefixDeletedUncoversPreviousMap}.
      */
     @Override
     protected void remove(String prefix) {
diff --git a/jena-arq/src/test/java/org/apache/jena/rdf_star/TestSPARQLStarExtra.java b/jena-arq/src/test/java/org/apache/jena/rdf_star/TestSPARQLStarExtra.java
index 7e39e22..3381623 100644
--- a/jena-arq/src/test/java/org/apache/jena/rdf_star/TestSPARQLStarExtra.java
+++ b/jena-arq/src/test/java/org/apache/jena/rdf_star/TestSPARQLStarExtra.java
@@ -23,17 +23,26 @@
 import org.apache.jena.riot.resultset.rw.ResultsReader;
 import org.junit.Test;
 
-/** 
- * Odds and ends for SPARQL*. 
+/**
+ * Odds and ends for SPARQL*.
  */
 public class TestSPARQLStarExtra {
 
     private static String FILES = "testing/ARQ/RDF-Star/Other/";
 
     // RDF4J format JSON results.
-    // It uses "s", "p" and "o" for the RDF term results. 
-    @Test public void parse_alt() {
-        String x = FILES+"alternate-results.srj";
+    // It uses "s", "p" and "o" for the RDF term results.
+    @Test public void parse_alt_1() {
+        String x = FILES+"alternate-results-1.srj";
+        ResultSet rs = ResultsReader.create().read(x);
+        ResultSetFormatter.consume(rs);
+    }
+
+    // Believed Stardog format JSON results.
+    // It uses "s", "p" and "o" for the RDF term results.
+    // It uses "statement" for "triple".
+    @Test public void parse_alt_2() {
+        String x = FILES+"alternate-results-2.srj";
         ResultSet rs = ResultsReader.create().read(x);
         ResultSetFormatter.consume(rs);
     }
diff --git a/jena-arq/src/test/java/org/apache/jena/riot/TestJsonLDReader.java b/jena-arq/src/test/java/org/apache/jena/riot/TestJsonLDReader.java
index 0ac52ef..80bb6ec 100644
--- a/jena-arq/src/test/java/org/apache/jena/riot/TestJsonLDReader.java
+++ b/jena-arq/src/test/java/org/apache/jena/riot/TestJsonLDReader.java
@@ -18,57 +18,50 @@
 
 package org.apache.jena.riot;
 
-import static org.junit.Assert.assertTrue;
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.github.jsonldjava.core.DocumentLoader;
+import com.github.jsonldjava.core.JsonLdOptions;
+import com.github.jsonldjava.utils.JsonUtils;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.DatasetFactory;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.riot.lang.JsonLDReader;
+import org.apache.jena.riot.system.ErrorHandlerFactory;
+import org.apache.jena.sparql.util.Context;
+import org.apache.jena.vocabulary.RDF;
+import org.junit.Test;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 
-import com.fasterxml.jackson.core.JsonGenerationException;
-import com.github.jsonldjava.utils.JsonUtils;
-
-import org.apache.jena.query.Dataset;
-import org.apache.jena.query.DatasetFactory;
-import org.apache.jena.rdf.model.Model;
-import org.apache.jena.rdf.model.ModelFactory;
-import org.apache.jena.riot.system.ErrorHandlerFactory;
-import org.apache.jena.sparql.util.Context;
-import org.apache.jena.vocabulary.RDF;
-import org.junit.Test;
+import static org.junit.Assert.assertTrue;
 
 public class TestJsonLDReader {
 
     @Test
-    public final void simpleReadTest() {
-        try {
-            String jsonld = someSchemaDorOrgJsonld();
-            Model m = ModelFactory.createDefaultModel();
-            RDFParser.create()
-                .errorHandler(ErrorHandlerFactory.errorHandlerNoLogging)
-                .fromString(jsonld)
-                .lang(Lang.JSONLD)
-                .parse(m);
-            assertJohnDoeIsOK(m);
-        } catch (RiotException e) {
-            // cf. org.apache.jena.riot.RiotException: loading remote context failed: http://schema.org/
-            // There's a line printed anyway
-            // e.printStackTrace();
-        }
+    public final void simpleReadTest() throws IOException {
+        String jsonld = someSchemaDorOrgJsonld();
+        Dataset ds = jsonld2dataset(jsonld, null);
+        assertJohnDoeIsOK(ds.getDefaultModel());
     }
 
-    /** Test using the jena Context mechanism to pass the jsonld "@context" */
-    @Test public final void overrideAtContextTest() throws JsonGenerationException, IOException {
+    /**
+     * Test using the jena Context mechanism to pass the jsonld "@context"
+     */
+    @Test
+    public final void overrideAtContextTest() throws JsonGenerationException, IOException {
         // some jsonld using schema.org's URI as "@context"
         String jsonld = someSchemaDorOrgJsonld();
 
         // a subset of schema.org that can be used as @context for jsonld
-        String jsonldContext = "{\"name\":{\"@id\":\"http://schema.org/name\"},\"Person\": {\"@id\": \"http://schema.org/Person\"}}";
+        String jsonldContext = "{\"name\":{\"@id\":\"http://schema.org/name\", \"@type\": \"http://www.w3.org/2001/XMLSchema#other\"},\"Person\": {\"@id\": \"http://schema.org/Person\"}}";
 
         // pass the jsonldContext to the read using a jena Context
         Context jenaCtx = new Context();
         Object jsonldContextAsMap = JsonUtils.fromInputStream(new ByteArrayInputStream(jsonldContext.getBytes(StandardCharsets.UTF_8)));
-        jenaCtx.set(RIOT.JSONLD_CONTEXT, jsonldContextAsMap);
+        jenaCtx.set(JsonLDReader.JSONLD_CONTEXT, jsonldContextAsMap);
 
         // read the jsonld, replacing its "@context"
         Dataset ds = jsonld2dataset(jsonld, jenaCtx);
@@ -77,33 +70,33 @@
         assertJohnDoeIsOK(ds.getDefaultModel());
     }
 
-    /** Not really useful, but one can replace the @context by a URI: in this case, this URI is used when expanding the json
-     * (letting JSON-LD java API taking care of downloading the context. */
-    // well, as of this writing, it doesn't work, as we get a "loading remote context failed"
-    // But it is about the replacing URI, not the replaced one, showing that the mechanism does work
-    @Test public final void overrideAtContextByURITest() throws JsonGenerationException, IOException {
+    /**
+     * Not really useful, but one can replace the @context by a URI: in this case, this URI is used when expanding the json
+     * (letting JSON-LD java API taking care of downloading the context.
+     */
+    @Test
+    public final void overrideJsonLdOptionsAndContextUri() throws JsonGenerationException, IOException {
         // some jsonld using a (fake) pseudo.schema.org's URI as "@context"
         String jsonld = "{\"@id\":\"_:b0\",\"@type\":\"Person\",\"name\":\"John Doe\",\"@context\":\"http://pseudo.schema.org/\"}";
 
         // a subset of schema.org that can be used as @context for jsonld
         String jsonldContext = "\"http://schema.org\"";
 
-        // pass the jsonldContext to the read using a jena Context
-        Context jenaCtx = new Context();
-        Object jsonldContextAsObject = JsonUtils.fromInputStream(new ByteArrayInputStream(jsonldContext.getBytes(StandardCharsets.UTF_8)));
-        jenaCtx.set(RIOT.JSONLD_CONTEXT, jsonldContextAsObject);
+        JsonLdOptions options = new JsonLdOptions();
+        DocumentLoader dl = new DocumentLoader();
+        dl.addInjectedDoc("http://schema.org", String.format("{%s}", schemaOrgContext()));
+        options.setDocumentLoader(dl);
 
-        try {
-            // read the jsonld, replacing its "@context"
-            Dataset ds = jsonld2dataset(jsonld, jenaCtx);
+        // pass the jsonldContext and JsonLdOptions to the read using a jena Context
+        JsonLDReadContext jenaCtx = new JsonLDReadContext();
+        jenaCtx.setJsonLDContext(jsonldContext);
+        jenaCtx.setOptions(options);
 
-            // check ds is correct
-            assertJohnDoeIsOK(ds.getDefaultModel());
-        } catch (RiotException e) {
-            // cf. org.apache.jena.riot.RiotException: loading remote context failed: http://schema.org/
-            // There's a line printed anyway
-            // e.printStackTrace();
-        }
+        // read the jsonld, replacing its "@context"
+        Dataset ds = jsonld2dataset(jsonld, jenaCtx);
+
+        // check ds is correct
+        assertJohnDoeIsOK(ds.getDefaultModel());
     }
 
     //
@@ -120,25 +113,34 @@
 
         try (InputStream in = new ByteArrayInputStream(jsonld.getBytes(StandardCharsets.UTF_8))) {
             RDFParser.create()
-                .source(in)
-                .errorHandler(ErrorHandlerFactory.errorHandlerNoLogging)
-                .lang(Lang.JSONLD)
-                .context(jenaCtx)
-                .parse(ds.asDatasetGraph());
+                    .source(in)
+                    .errorHandler(ErrorHandlerFactory.errorHandlerNoLogging)
+                    .lang(Lang.JSONLD)
+                    .context(jenaCtx)
+                    .parse(ds.asDatasetGraph());
         }
 
         return ds;
     }
 
-    /** Example data */
+    /**
+     * Example data
+     */
     private String someSchemaDorOrgJsonld() {
-        return "{\"@id\":\"_:b0\",\"@type\":\"Person\",\"name\":\"John Doe\",\"@context\":\"http://schema.org/\"}";
+        return String.format("{\"@id\": \"_:b0\", \"@type\": \"Person\", \"name\": \"John Doe\", %s }", schemaOrgContext());
     }
 
-    /** Checking that the data loaded from someSchemaDorOrgJsonld into a model, is OK */
+    private String schemaOrgContext() {
+        return "\"@context\": {\"@vocab\": \"http://schema.org/\", \"name\": {\"@type\": \"http://www.w3.org/2001/XMLSchema#other\"} }";
+    }
+
+    /**
+     * Checking that the data loaded from someSchemaDorOrgJsonld into a model, is OK
+     */
     private void assertJohnDoeIsOK(Model m) {
         assertTrue(m.contains(null, RDF.type, m.createResource("http://schema.org/Person")));
-        assertTrue(m.contains(null, m.createProperty("http://schema.org/name"), "John Doe"));       
+        assertTrue(m.contains(null, m.createProperty("http://schema.org/name"),
+                m.createTypedLiteral("John Doe", "http://www.w3.org/2001/XMLSchema#other")));
     }
 
 
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/core/TestSpecialGraphNames.java b/jena-arq/src/test/java/org/apache/jena/sparql/core/TestSpecialGraphNames.java
index 66c97e7..998e721 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/core/TestSpecialGraphNames.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/core/TestSpecialGraphNames.java
@@ -84,6 +84,7 @@
         Op op = op("(bgp (?s ?p ?o))", mode) ;
         List<Binding> results = exec(op) ;
         assertEquals(5, results.size()) ;
+        @SuppressWarnings("deprecation")
         Op op2 = Algebra.unionDefaultGraph(op) ;
         List<Binding> results2 = exec(op2) ;
         assertEquals(4, results2.size()) ;
diff --git a/jena-arq/testing/ARQ/RDF-Star/Other/README b/jena-arq/testing/ARQ/RDF-Star/Other/README
new file mode 100644
index 0000000..218fb62
--- /dev/null
+++ b/jena-arq/testing/ARQ/RDF-Star/Other/README
@@ -0,0 +1,4 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+SPARQL* JSON Result forms from other systems.
+ARQ can read these variations.
diff --git a/jena-arq/testing/ARQ/RDF-Star/Other/alternate-results.srj b/jena-arq/testing/ARQ/RDF-Star/Other/alternate-results-1.srj
similarity index 100%
rename from jena-arq/testing/ARQ/RDF-Star/Other/alternate-results.srj
rename to jena-arq/testing/ARQ/RDF-Star/Other/alternate-results-1.srj
diff --git a/jena-arq/testing/ARQ/RDF-Star/Other/alternate-results-2.srj b/jena-arq/testing/ARQ/RDF-Star/Other/alternate-results-2.srj
new file mode 100644
index 0000000..f7affa9
--- /dev/null
+++ b/jena-arq/testing/ARQ/RDF-Star/Other/alternate-results-2.srj
@@ -0,0 +1,41 @@
+{
+  "head" : {
+    "vars" : [
+      "a",
+      "b",
+      "c"
+    ]
+  },
+  "results" : {
+    "bindings": [
+      { "a" : {
+          "type" : "statement",
+          "value" : {
+            "s" : {
+              "type" : "uri",
+              "value" : "http://example.org/bob"
+            },
+            "p" : {
+              "type" : "uri",
+              "value" : "http://xmlns.com/foaf/0.1/name"
+            },
+            "o" : {
+              "datatype" : "http://www.w3.org/2001/XMLSchema#integer",
+              "type" : "literal",
+              "value" : "23"
+            }
+          }
+        },
+        "b": { 
+          "type": "uri",
+          "value": "http://example.org/certainty"
+        },
+        "c" : {
+          "datatype" : "http://www.w3.org/2001/XMLSchema#decimal",
+          "type" : "literal",
+          "value" : "0.9"
+        }
+      }
+    ]
+  }
+}
diff --git a/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/data4.ttl b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/data4.ttl
new file mode 100644
index 0000000..ca7eae0
--- /dev/null
+++ b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/data4.ttl
@@ -0,0 +1,12 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX rdf:    <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:   <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX :       <http://example/>
+
+# One triple with base triple.
+:s :p :o .
+<<:s :p :o>> :q 123 .
+
+# One triple term without base triple.
+<<:x :p :z>> :q 456 .
diff --git a/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/manifest.ttl b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/manifest.ttl
index e9eb664..03bd9a1 100644
--- a/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/manifest.ttl
+++ b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/manifest.ttl
@@ -49,6 +49,9 @@
         :sparql-star-pattern-6
         :sparql-star-pattern-7
         :sparql-star-pattern-8
+        :sparql-star-pattern-9
+
+        :sparql-star-pattern-arq-1
 
         :sparql-star-find-1
 
@@ -236,6 +239,24 @@
     mf:result  <sparql-star-pattern-09.srj>
 .
 
+## SPARQL* : ARQ specific.
+
+:sparql-star-pattern-arq-1
+    mf:name    "SPARQL* - Base triple in data: ground triple term in query" ;
+    mf:action
+        [ qt:query  <sparql-star-pattern-arq-01.arq> ;
+          qt:data   <data4.ttl> ] ;
+    mf:result  <sparql-star-pattern-arq-01.srj>
+.
+
+:sparql-star-pattern-arq-1
+    mf:name    "SPARQL* - Base triple in data: pattern triple term in query" ;
+    mf:action
+        [ qt:query  <sparql-star-pattern-arq-01.arq> ;
+          qt:data   <data4.ttl> ] ;
+    mf:result  <sparql-star-pattern-arq-01.srj>
+.
+
 ## SPARQL FIND*
 
 :sparql-star-find-1
diff --git a/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-01.arq b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-01.arq
new file mode 100644
index 0000000..b774c51
--- /dev/null
+++ b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-01.arq
@@ -0,0 +1,9 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX rdf:    <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:   <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX :       <http://example/>
+
+SELECT * {
+    <<:x :p :z>> :q 456 .
+}
diff --git a/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-01.srj b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-01.srj
new file mode 100644
index 0000000..3edf012
--- /dev/null
+++ b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-01.srj
@@ -0,0 +1,9 @@
+{ "head": {
+    "vars": [  ]
+  } ,
+  "results": {
+    "bindings": [
+      
+    ]
+  }
+}
diff --git a/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-02.arq b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-02.arq
new file mode 100644
index 0000000..3cb9af9
--- /dev/null
+++ b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-02.arq
@@ -0,0 +1,11 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX rdf:    <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:   <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX :       <http://example/>
+
+SELECT * {
+    { <<:s :p ?o1 >> :q ?v1 . }
+    UNION
+    { <<:x :p ?o2 >> :q ?v2 . }
+}
diff --git a/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-02.srj b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-02.srj
new file mode 100644
index 0000000..de25bbe
--- /dev/null
+++ b/jena-arq/testing/ARQ/RDF-Star/SPARQL-Star/sparql-star-pattern-arq-02.srj
@@ -0,0 +1,12 @@
+{ "head": {
+    "vars": [ "o1" , "v1" , "o2" , "v2" ]
+  } ,
+  "results": {
+    "bindings": [
+      { 
+        "o1": { "type": "uri" , "value": "http://example/o" } ,
+        "v1": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "123" }
+      }
+    ]
+  }
+}
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/CollectionUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/CollectionUtils.java
index 9cbf807..7f63718 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/CollectionUtils.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/CollectionUtils.java
@@ -19,18 +19,23 @@
 package org.apache.jena.atlas.lib;
 
 import java.util.Collection ;
+import java.util.List;
 
 import org.apache.jena.atlas.iterator.Iter;
 
 public class CollectionUtils
-{   
-    /** Test for same elements, regardless of cardinality */ 
+{
+    /** Test for same elements, regardless of cardinality */
     public static <T> boolean sameElts(Collection<T> left, Collection<T> right) {
         return right.containsAll(left) && left.containsAll(right) ;
     }
 
-    /** Return an element from a collection. */ 
+    /** Return an element from a collection. */
     public static <T> T oneElt(Collection<T> collection) {
+        if ( collection == null || collection.isEmpty() )
+            return null;
+        if ( collection instanceof List<?> )
+            return ((List<T>)collection).get(0);
         return Iter.first(collection.iterator());
     }
 }
diff --git a/jena-cmds/src/main/java/arq/cmdline/ModQueryIn.java b/jena-cmds/src/main/java/arq/cmdline/ModQueryIn.java
index 26e41e3..4ddaf12 100644
--- a/jena-cmds/src/main/java/arq/cmdline/ModQueryIn.java
+++ b/jena-cmds/src/main/java/arq/cmdline/ModQueryIn.java
@@ -20,17 +20,12 @@
 
 import java.io.IOException ;
 
-import jena.cmd.ArgDecl;
-import jena.cmd.CmdArgModule;
-import jena.cmd.CmdException;
-import jena.cmd.CmdGeneral;
-import jena.cmd.ModBase;
-import jena.cmd.TerminationException;
-
+import jena.cmd.*;
 import org.apache.jena.query.Query ;
 import org.apache.jena.query.QueryFactory ;
 import org.apache.jena.query.Syntax ;
 import org.apache.jena.shared.JenaException ;
+import org.apache.jena.shared.NotFoundException;
 import org.apache.jena.sparql.ARQInternalErrorException ;
 import org.apache.jena.util.FileUtils ;
 
@@ -127,8 +122,12 @@
                         throw new CmdException("Error reading stdin", ex) ;
                     }
                 } else {
-                    query = QueryFactory.read(queryFilename, baseURI, getQuerySyntax()) ;
-                    return query ;
+                    try {
+                        query = QueryFactory.read(queryFilename, baseURI, getQuerySyntax()) ;
+                        return query ;
+                    } catch (NotFoundException ex) {
+                        throw new JenaException("Failed to load Query: "+ex.getMessage());
+                    }
                 }
             }
 
diff --git a/jena-cmds/src/main/java/arq/query.java b/jena-cmds/src/main/java/arq/query.java
index 898513d..67562d7 100644
--- a/jena-cmds/src/main/java/arq/query.java
+++ b/jena-cmds/src/main/java/arq/query.java
@@ -30,6 +30,7 @@
 import org.apache.jena.riot.RiotNotFoundException ;
 import org.apache.jena.riot.SysRIOT ;
 import org.apache.jena.shared.JenaException ;
+import org.apache.jena.shared.NotFoundException;
 import org.apache.jena.sparql.ARQInternalErrorException ;
 import org.apache.jena.sparql.core.Transactional ;
 import org.apache.jena.sparql.core.TransactionalNull;
@@ -45,10 +46,10 @@
     private ArgDecl argExplain  = new ArgDecl(ArgDecl.NoValue, "explain") ;
     private ArgDecl argOptimize = new ArgDecl(ArgDecl.HasValue, "opt", "optimize") ;
 
-    protected int repeatCount = 1 ; 
+    protected int repeatCount = 1 ;
     protected int warmupCount = 0 ;
     protected boolean queryOptimization = true ;
-    
+
     protected ModTime       modTime =     new ModTime() ;
     protected ModQueryIn    modQuery =    null;
     protected ModDataset    modDataset =  null ;
@@ -63,7 +64,7 @@
     public query(String[] argv)
     {
         super(argv) ;
-        modQuery = new ModQueryIn(getDefaultSyntax()) ; 
+        modQuery = new ModQueryIn(getDefaultSyntax()) ;
         modDataset = setModDataset() ;
 
         super.addModule(modQuery) ;
@@ -86,7 +87,7 @@
      *  <li>Command default</li>
      *  <li>System default</li>
      *  </ul>
-     *  
+     *
      */
     protected Syntax getDefaultSyntax()     { return Syntax.defaultQuerySyntax ; }
 
@@ -97,7 +98,7 @@
         if ( contains(argRepeat) )
         {
             String[] x = getValue(argRepeat).split(",") ;
-            if ( x.length == 1 ) 
+            if ( x.length == 1 )
             {
                 try { repeatCount = Integer.parseInt(x[0]) ; }
                 catch (NumberFormatException ex)
@@ -115,26 +116,26 @@
         }
         if ( isVerbose() )
             ARQ.getContext().setTrue(ARQ.symLogExec) ;
-        
+
         if ( hasArg(argExplain) )
             ARQ.setExecutionLogging(Explain.InfoLevel.ALL) ;
-        
+
         if ( hasArg(argOptimize) )
         {
             String x1 = getValue(argOptimize) ;
-            if ( hasValueOfTrue(argOptimize) || x1.equalsIgnoreCase("on") || x1.equalsIgnoreCase("yes") ) 
+            if ( hasValueOfTrue(argOptimize) || x1.equalsIgnoreCase("on") || x1.equalsIgnoreCase("yes") )
                 queryOptimization = true ;
             else if ( hasValueOfFalse(argOptimize) || x1.equalsIgnoreCase("off") || x1.equalsIgnoreCase("no") )
                 queryOptimization = false ;
             else throw new CmdException("Optimization flag must be true/false/on/off/yes/no. Found: "+getValue(argOptimize)) ;
         }
     }
-    
+
     protected ModDataset setModDataset()
     {
         return new ModDatasetGeneralAssembler() ;
     }
-    
+
     @Override
     protected void exec()
     {
@@ -142,16 +143,16 @@
             ARQ.getContext().setFalse(ARQ.optimization) ;
         if ( cmdStrictMode )
             ARQ.getContext().setFalse(ARQ.optimization) ;
-        
+
         // Warm up.
         for ( int i = 0 ; i < warmupCount ; i++ )
         {
             queryExec(false, ResultsFormat.FMT_NONE) ;
         }
-        
+
         for ( int i = 0 ; i < repeatCount ; i++ )
             queryExec(modTime.timingEnabled(),  modResults.getResultsFormat()) ;
-        
+
         if ( modTime.timingEnabled() && repeatCount > 1 )
         {
             long avg = totalTime/repeatCount ;
@@ -162,16 +163,16 @@
 
     @Override
     protected String getCommandName() { return Lib.className(this) ; }
-    
+
     @Override
     protected String getSummary() { return getCommandName()+" --data=<file> --query=<query>" ; }
-    
+
     /** Choose the dataset.
      * <li> use the data as described on the command line
      * <li> else use FROM/FROM NAMED if present (pass null to ARQ)
      * <li> else provided an empty dataset and hope the query has VALUES/BIND
      */
-    protected Dataset getDataset(Query query)  { 
+    protected Dataset getDataset(Query query)  {
         try {
             Dataset ds = modDataset.getDataset();
             if ( ds == null )
@@ -187,20 +188,29 @@
             throw new TerminationException(1);
         }
     }
-    
-    // Policy for no command line dataset. null means "whatever" (use FROM) 
+
+    protected Query getQuery() {
+        try {
+            return modQuery.getQuery() ;
+        } catch (NotFoundException ex) {
+            System.err.println("Failed to load query: "+ex.getMessage());
+            throw new TerminationException(1);
+        }
+    }
+
+    // Policy for no command line dataset. null means "whatever" (use FROM)
     protected Dataset dealWithNoDataset(Query query)  {
         if ( query.hasDatasetDescription() )
             return null;
         return DatasetFactory.createTxnMem();
-        //throw new CmdException("No dataset provided") ; 
+        //throw new CmdException("No dataset provided") ;
     }
-    
+
     protected long totalTime = 0 ;
     protected void queryExec(boolean timed, ResultsFormat fmt)
     {
         try {
-            Query query = modQuery.getQuery() ;
+            Query query = getQuery() ;
             if ( isVerbose() ) {
                 IndentedWriter out = new IndentedWriter(System.err, true);
                 query.serialize(out);
@@ -208,7 +218,7 @@
                 out.println();
                 out.flush();
             }
-            
+
             if ( isQuiet() )
                 LogCtl.setError(SysRIOT.riotLoggerName) ;
             Dataset dataset = getDataset(query) ;
diff --git a/jena-core/src/main/java/org/apache/jena/assembler/JA.java b/jena-core/src/main/java/org/apache/jena/assembler/JA.java
index 3e65085..5e6947f 100644
--- a/jena-core/src/main/java/org/apache/jena/assembler/JA.java
+++ b/jena-core/src/main/java/org/apache/jena/assembler/JA.java
@@ -82,13 +82,13 @@
     public static final Property data = property( "data" );
 
     public static final Property content = property( "content" );
-    
+
     public static final Property context = property( "context" );
 
     public static final Property cxtName = property( "cxtName" );
 
     public static final Property cxtValue = property( "cxtValue" );
-    
+
     public static final Resource ExternalContent = resource( "ExternalContent" );
 
     public static final Property externalContent = property( "externalContent" );
diff --git a/jena-core/src/main/java/org/apache/jena/graph/impl/LiteralLabelImpl.java b/jena-core/src/main/java/org/apache/jena/graph/impl/LiteralLabelImpl.java
index bc5423c..cfbb262 100644
--- a/jena-core/src/main/java/org/apache/jena/graph/impl/LiteralLabelImpl.java
+++ b/jena-core/src/main/java/org/apache/jena/graph/impl/LiteralLabelImpl.java
@@ -18,6 +18,7 @@
 
 package org.apache.jena.graph.impl;
 
+import java.util.Arrays;
 import java.util.Locale ;
 import java.util.Objects ;
 
@@ -303,20 +304,64 @@
 		return lexicalForm;
 	}
     
-    /** 
-     	Answer the value used to index this literal
-        TODO Consider pushing indexing decisions down to the datatype
-    */
+    /**
+     * Answer an object used to index this literal. This object must provide
+     * {@link Object#equals} and {@link Object#hashCode} based on values, not object
+     * instance identity.
+     */
     @Override
     public Object getIndexingValue() {
-        return
-            isXML() ? this
-            : !lang.equals( "" ) ? getLexicalForm() + "@" + lang.toLowerCase(Locale.ROOT)
-            : wellformed ? getValue()
-            : getLexicalForm() 
-            ;
+        if ( isXML() )
+            return this;
+        if ( !lang.equals("") )
+            return getLexicalForm() + "@" + lang.toLowerCase(Locale.ROOT);
+        if ( wellformed ) {
+            Object value = getValue();
+            // JENA-1936
+            // byte[] does not provide hashCode/equals based on the contents of the array.
+            if ( value instanceof byte[] )
+                return new ByteArray((byte[])value);
+            return value;
+        }
+        return getLexicalForm();
     }
 
+    /**
+     * {@code byte[]} wrapper that provides {@code hashCode} and {@code equals} based
+     * on the value of the array. This assumes the {@code byte[]} is not changed
+     * (which is the case for literals with binary value).
+     */
+    static class ByteArray {
+        private int hashCode = 0 ;
+        
+        private final byte[] bytes;
+        /*package*/ ByteArray(byte[] bytes) {
+            this.bytes = bytes;
+        }
+
+        @Override
+        public int hashCode() {
+            if ( hashCode == 0 ) {
+                final int prime = 31;
+                int result = 1;
+                hashCode = prime * result + Arrays.hashCode(bytes);
+            }
+            return hashCode;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if ( this == obj )
+                return true;
+            if ( obj == null )
+                return false;
+            if ( getClass() != obj.getClass() )
+                return false;
+            ByteArray other = (ByteArray)obj;
+            return Arrays.equals(bytes, other.bytes);
+        }
+    }
+    
 	/** 
      	Answer the language associated with this literal (the empty string if
         there's no language).
diff --git a/jena-core/src/test/java/org/apache/jena/graph/test/TestNode.java b/jena-core/src/test/java/org/apache/jena/graph/test/TestNode.java
index 1d2907f..53ea93a 100644
--- a/jena-core/src/test/java/org/apache/jena/graph/test/TestNode.java
+++ b/jena-core/src/test/java/org/apache/jena/graph/test/TestNode.java
@@ -22,6 +22,7 @@
 import junit.framework.TestSuite ;
 
 import org.apache.jena.JenaRuntime ;
+import org.apache.jena.atlas.lib.Creator;
 import org.apache.jena.datatypes.RDFDatatype ;
 import org.apache.jena.datatypes.TypeMapper ;
 import org.apache.jena.datatypes.xsd.XSDDatatype ;
@@ -606,17 +607,40 @@
     }
 
     public void testGetIndexingValuePlainString()
-    { testIndexingValueLiteral( NodeCreateUtils.create( "'literally'" ) ); }
+    { testIndexingValueLiteral( ()->NodeCreateUtils.create( "'literally'" ) ); }
 
     public void testGetIndexingValueLanguagedString()
-    { testIndexingValueLiteral( NodeCreateUtils.create( "'chat'fr" ) ); }
+    { testIndexingValueLiteral( ()->NodeCreateUtils.create( "'chat'fr" ) ); }
 
     public void testGetIndexingValueXSDString()
-    { testIndexingValueLiteral( NodeCreateUtils.create( "'string'xsd:string" ) ); }
+    { testIndexingValueLiteral( ()->NodeCreateUtils.create( "'string'xsd:string" ) ); }
 
-    private void testIndexingValueLiteral( Node s )
-    { assertEquals( s.getLiteral().getIndexingValue(), s.getIndexingValue() ); }
+    // JENA-1936
+    public void testGetIndexingValueHexBinary1()
+    { testIndexingValueLiteral( ()->NodeCreateUtils.create( "''xsd:hexBinary" ) ); }
 
+    public void testGetIndexingValueHexBinary2()
+    { testIndexingValueLiteral( ()->NodeCreateUtils.create( "'ABCD'xsd:hexBinary" ) ); }
+
+    public void testGetIndexingValueBase64Binary1()
+    { testIndexingValueLiteral( ()->NodeCreateUtils.create( "''xsd:base64Binary" ) ); }
+
+    // "sure." encodes to "c3VyZS4=" 
+    public void testGetIndexingValueBase64Binary2()
+    { testIndexingValueLiteral( ()->NodeCreateUtils.create( "'c3VyZS4='xsd:base64Binary" ) ); }
+    
+    private void testIndexingValueLiteral( Creator<Node> creator) {
+        Node n1 = creator.create();
+        Node n2 = creator.create();
+        testIndexingValueLiteral(n1,n2);
+    }
+    
+    private void testIndexingValueLiteral(Node n1, Node n2) {
+        assertNotSame(n1, n2); // Test the test.
+        assertEquals(n1.getLiteral().getIndexingValue(), n2.getIndexingValue());
+        assertEquals(n1.getLiteral().getIndexingValue().hashCode(), n2.getIndexingValue().hashCode());
+    }
+    
     public void  testGetLiteralValuePlainString()
     {
         Node s = NodeCreateUtils.create( "'aString'" );
diff --git a/jena-core/src/test/java/org/apache/jena/graph/test/TestNodeExt.java b/jena-core/src/test/java/org/apache/jena/graph/test/TestNodeExt.java
index 3698475..ac1e0d2 100644
--- a/jena-core/src/test/java/org/apache/jena/graph/test/TestNodeExt.java
+++ b/jena-core/src/test/java/org/apache/jena/graph/test/TestNodeExt.java
@@ -35,8 +35,12 @@
 
     private static Triple triple9 = Triple.create(NodeFactory.createBlankNode(),p,o);
 
-    @Test public void ext_triple_1() {
-        Node_Triple nt = new Node_Triple(s,p,o);
+    private static Node_Triple newTripleTerm(Triple triple)             { return new Node_Triple(triple); }
+
+    private static Node_Triple newTripleTerm(Node s, Node p , Node o)   { return new Node_Triple(s,p,o); }
+
+     @Test public void ext_triple_1() {
+        Node_Triple nt = newTripleTerm(s,p,o);
         assertNotNull(nt.get());
         assertNotNull(Node_Triple.tripleOrNull(nt));
         assertNotNull(Node_Triple.triple(nt));
@@ -48,8 +52,8 @@
     }
 
     @Test public void ext_triple_2() {
-        Node_Triple nt1 = new Node_Triple(s,p,o);
-        Node_Triple nt2 = new Node_Triple(s,p,o);
+        Node_Triple nt1 = newTripleTerm(s,p,o);
+        Node_Triple nt2 = newTripleTerm(s,p,o);
 
         assertEquals(nt1, nt2);
         assertEquals(nt1.hashCode(), nt2.hashCode());
@@ -57,8 +61,8 @@
     }
 
     @Test public void ext_triple_3() {
-        Node_Triple nt1 = new Node_Triple(triple1);
-        Node_Triple nt2 = new Node_Triple(triple2);
+        Node_Triple nt1 = newTripleTerm(triple1);
+        Node_Triple nt2 = newTripleTerm(triple2);
         assertNotSame(nt1.get(), nt2.get());
         assertNotSame(nt1, nt2);
         assertEquals(nt1, nt2);
@@ -66,8 +70,8 @@
     }
 
     @Test public void ext_triple_4() {
-        Node_Triple nt1 = new Node_Triple(triple1);
-        Node_Triple nt9 = new Node_Triple(triple9);
+        Node_Triple nt1 = newTripleTerm(triple1);
+        Node_Triple nt9 = newTripleTerm(triple9);
         assertNotSame(nt1.get(), nt9.get());
         assertNotSame(nt1, nt9);
         assertNotEquals(nt1, nt9);
@@ -90,6 +94,4 @@
         Node n = NodeFactory.createLiteral("abc");
         Node_Triple.triple(n);
     }
-
-
 }
diff --git a/jena-core/src/test/java/org/apache/jena/graph/test/TestTypedLiterals.java b/jena-core/src/test/java/org/apache/jena/graph/test/TestTypedLiterals.java
index 91350c3..a81cc33 100644
--- a/jena-core/src/test/java/org/apache/jena/graph/test/TestTypedLiterals.java
+++ b/jena-core/src/test/java/org/apache/jena/graph/test/TestTypedLiterals.java
@@ -954,6 +954,42 @@
         Literal l = m.createTypedLiteral(data, XSDDatatype.XSDhexBinary);
         assertEquals("hexBinary encoding", "0FB7", l.getLexicalForm());
     }
+    
+    public void testBinaryIndexing1() {
+        Literal x1 = m.createTypedLiteral("", XSDDatatype.XSDbase64Binary);
+        Literal x2 = m.createTypedLiteral("", XSDDatatype.XSDbase64Binary);
+        assertEquals("base64Binary indexing hashCode", 
+                     x1.asNode().getIndexingValue().hashCode(),
+                     x2.asNode().getIndexingValue().hashCode());
+        assertEquals("base64Binary indexing", x1.asNode().getIndexingValue(), x2.asNode().getIndexingValue());  
+    }
+
+    public void testBinaryIndexing2() {
+        Literal x1 = m.createTypedLiteral("GpM7", XSDDatatype.XSDbase64Binary);
+        Literal x2 = m.createTypedLiteral("GpM7", XSDDatatype.XSDbase64Binary);
+        assertEquals("base64Binary indexing hashCode", 
+            x1.asNode().getIndexingValue().hashCode(),
+            x2.asNode().getIndexingValue().hashCode());
+        assertEquals("base64Binary indexing", x1.asNode().getIndexingValue(), x2.asNode().getIndexingValue());  
+    }
+    
+    public void testBinaryIndexing3() {
+        Literal x1 = m.createTypedLiteral("", XSDDatatype.XSDhexBinary);
+        Literal x2 = m.createTypedLiteral("", XSDDatatype.XSDhexBinary);
+        assertEquals("hexBinary indexing hashCode", 
+            x1.asNode().getIndexingValue().hashCode(),
+            x2.asNode().getIndexingValue().hashCode());
+        assertEquals("hexBinary indexing", x1.asNode().getIndexingValue(), x2.asNode().getIndexingValue());  
+    }
+
+    public void testBinaryIndexing4() {
+        Literal x1 = m.createTypedLiteral("AABB", XSDDatatype.XSDhexBinary);
+        Literal x2 = m.createTypedLiteral("AABB", XSDDatatype.XSDhexBinary);
+        assertEquals("hexBinary indexing hashCode", 
+            x1.asNode().getIndexingValue().hashCode(),
+            x2.asNode().getIndexingValue().hashCode());
+        assertEquals("hexBinary indexing", x1.asNode().getIndexingValue(), x2.asNode().getIndexingValue());  
+    }
 
     /** Test that XSD anyURI is not sameValueAs XSD string (Xerces returns a string as the value for both) */
     public void testXSDanyURI() {
diff --git a/jena-db/jena-dboe-storage/src/main/java/org/apache/jena/dboe/storage/prefixes/PrefixMapI.java b/jena-db/jena-dboe-storage/src/main/java/org/apache/jena/dboe/storage/prefixes/PrefixMapI.java
index 30ea998..1a71853 100644
--- a/jena-db/jena-dboe-storage/src/main/java/org/apache/jena/dboe/storage/prefixes/PrefixMapI.java
+++ b/jena-db/jena-dboe-storage/src/main/java/org/apache/jena/dboe/storage/prefixes/PrefixMapI.java
@@ -36,7 +36,7 @@
  * The contract does not require an implementation to do any validation unlike the
  * Jena Core {@link PrefixMapping} which requires validation of prefixes.
  * </p>
- * 
+ *
  * @implNote The package {@code org.apache.jena.dboe.storage.prefixes} in module
  *     {@code jena-dboe-storage} provides implementations that work with
  *     {@code StoragePrefixes} which is dataset provision of prefixes on per-named
@@ -68,7 +68,7 @@
     public default void putAll(PrefixMap pmap) {
         pmap.forEach(this::add);
     }
-    
+
     @Override
     public default void putAll(PrefixMapping pmap) {
         pmap.getNsPrefixMap().forEach(this::add);
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/SolverRX.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/SolverRX.java
index f43d2aa..37a30f6 100644
--- a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/SolverRX.java
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/solver/SolverRX.java
@@ -69,7 +69,7 @@
         VarAlloc varAlloc = VarAlloc.get(context, ARQConstants.sysVarAllocRDFStar);
         if ( varAlloc == null ) {
             varAlloc = new VarAlloc(ARQConstants.allocVarTripleTerm);
-            context.set(ARQConstants.sysVarAllocRDFStar, varAlloc);  
+            context.set(ARQConstants.sysVarAllocRDFStar, varAlloc);
         }
         return varAlloc;
     }
@@ -80,7 +80,7 @@
                                                   ExecutionContext execCxt) {
         if ( ! tripleHasNodeTriple(pattern) )
             SolverLib.solve(nodeTupleTable, pattern, anyGraph, chain, filter, execCxt);
-        
+
         Args args = new Args(nodeTupleTable, anyGraph, filter, execCxt);
         return rdfStarTriple(chain, pattern, args);
     }
@@ -92,7 +92,7 @@
      * within {@link #rdfStarTriple} for nested triple term and a temporary allocated
      * variable as well can for {@code FIND(<<...>> AS ?t)}.
      *
-     * @implNote 
+     * @implNote
      * Without RDF*, this would be a plain call of {@link #matchData} which
      * is simply a call to {@link SolverLib#solve}.
      */
@@ -148,7 +148,7 @@
         Node subject1 = null;
         Node object1 = null;
 
-        if ( subject.isNodeTriple() && ! subject.isConcrete() ) {
+        if ( subject.isNodeTriple() ) {
             Triple tripleTerm = triple(subject);
             Var var = args.varAlloc.allocVar();
             patternTuple = createTuple(patternTuple, var, sIdx);
@@ -157,7 +157,7 @@
             subject1 = var;
         }
 
-        if ( object.isNodeTriple() && ! object.isConcrete() ) {
+        if ( object.isNodeTriple() ) {
             Triple tripleTerm = triple(object);
             Var var = args.varAlloc.allocVar();
             patternTuple = createTuple(patternTuple, var, oIdx);
@@ -221,7 +221,7 @@
                 return binding;
             return null;
         }
-        
+
         BindingNodeId b2 = new BindingNodeId(binding);
         b2.put(var, tid);
         return b2;
@@ -305,11 +305,11 @@
 
     private static Tuple<Node> tuple(Tuple<Node> base, Triple triple) {
         switch(base.len()){
-            case 3: 
-                return TupleFactory.create3(triple.getSubject(), triple.getPredicate(), triple.getObject());   
+            case 3:
+                return TupleFactory.create3(triple.getSubject(), triple.getPredicate(), triple.getObject());
             case 4:
                 return TupleFactory.create4(base.get(0), triple.getSubject(), triple.getPredicate(), triple.getObject());
             default:
-        }       throw new TDBException("Tuple not of length 3 or 4"); 
+        }       throw new TDBException("Tuple not of length 3 or 4");
     }
 }
\ No newline at end of file
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableInline.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableInline.java
index f7d7671..8c7fe0f 100644
--- a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableInline.java
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableInline.java
@@ -18,7 +18,6 @@
 
 package org.apache.jena.tdb2.store.nodetable;
 
-
 import org.apache.jena.graph.Node;
 import org.apache.jena.tdb2.store.NodeId;
 
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/sys/StoreConnection.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/sys/StoreConnection.java
index cb38cd2..249b2bc 100644
--- a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/sys/StoreConnection.java
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/sys/StoreConnection.java
@@ -82,7 +82,7 @@
         if ( sConn == null ) {
             ProcessFileLock lock = null;
             // This is not duplicating DatabaseConnection.build.
-            // This is a tdb.lock file in the storage database, not the switchable. 
+            // This is a tdb.lock file in the storage database, not the switchable.
             if (SystemTDB.DiskLocationMultiJvmUsagePrevention && ! location.isMem() ) {
                 lock = lockForLocation(location);
                 // Take the lock.  This is atomic and non-reentrant.
diff --git a/jena-db/jena-tdb2/src/test/java/org/apache/jena/tdb2/sys/TestDatabaseOps.java b/jena-db/jena-tdb2/src/test/java/org/apache/jena/tdb2/sys/TestDatabaseOps.java
index 91c6bb7..0e7760b 100644
--- a/jena-db/jena-tdb2/src/test/java/org/apache/jena/tdb2/sys/TestDatabaseOps.java
+++ b/jena-db/jena-tdb2/src/test/java/org/apache/jena/tdb2/sys/TestDatabaseOps.java
@@ -126,11 +126,19 @@
 
     @Test public void compact_prefixes_3() {
         // 2020-04:
-        // This test fails at "HERE" sometimes with an NPE at the marked line when run with Java14 on ASF Jenkins.
+        // This test fails at "HERE" in the test, with an NPEwith Java14 on ASF Jenkins.
+        // java.lang.NullPointerException
+        //         at java.base/java.nio.file.Files.provider(Files.java:105)
+        //         at java.base/java.nio.file.Files.isDirectory(Files.java:2308)
+        //         at org.apache.jena.tdb2.sys.IOX.asLocation(IOX.java:151)
+        //         at org.apache.jena.tdb2.sys.DatabaseOps.compact(DatabaseOps.java:176)
+        //         at org.apache.jena.tdb2.DatabaseMgr.compact(DatabaseMgr.java:62)
+        //         at org.apache.jena.tdb2.sys.TestDatabaseOps.compact_prefixes_3_test(TestDatabaseOps.java:157)
+        //         at org.apache.jena.tdb2.sys.TestDatabaseOps.compact_prefixes_3(TestDatabaseOps.java:135)
+        // (and now JDK15).
         // The NPE is from java.nio.file.Files.provider.
-        // It does not fail anywhere else and it does not always fail.
-        //   This suggests it is an environmental issue with the JDK14 job.
-        // Attempt to find out what is going on:
+        // It does not fail anywhere else ecept at ASF and it does not always fail.
+        // This seems to be on specific, maybe just on, Jenkins build node.
         try {
             compact_prefixes_3_test();
         } catch (Throwable th) {
diff --git a/jena-fuseki2/examples/README.md b/jena-fuseki2/examples/README.md
new file mode 100644
index 0000000..197a368
--- /dev/null
+++ b/jena-fuseki2/examples/README.md
@@ -0,0 +1,55 @@
+---
+title: Fuseki: Configuration file examples.
+---
+
+This directory includes some examples - they will need to be adapted to
+local requirements.
+
+_Configuration: Named services_
+
+* [config-1-mem.ttl](config-1-mem.ttl)
+* [config-2-mem-old.ttl](config-2-mem-old.ttl)
+
+_Configuration: Unnamed services_
+
+* [config-3-dataset-endpoints.ttl](config-3-dataset-endpoints.ttl)
+
+_Single endpoint "sparql" with multiple operations
+
+* [config-4-endpoint-sparql.ttl](config-4-endpoint-sparql.ttl)
+
+_TDB examples_
+
+* [config-tdb1.ttl](config-tdb1.ttl)
+* [config-tdb2.ttl](config-tdb2.ttl)
+
+TDB with text index:
+
+* [config-text-tdb2.ttl](config-text-tdb2.ttl)
+
+Selecting one graph from a TDB dataset
+
+tdb2-select-graphs-alt.ttl
+tdb2-select-graphs.ttl
+
+_SHACL service_
+
+* [config-shacl.ttl](config-shacl.ttl)
+
+_Query timeout_
+
+* [config-timeout-dataset.ttl](config-timeout-dataset.ttl)
+* [config-timeout-endpoint.ttl](config-timeout-endpoint.ttl)
+* [config-timeout-server.ttl](config-timeout-server.ttl)
+
+_Inference examples_
+
+* [config-inference-1.ttl](config-inference-1.ttl)
+* [config-inference-2.ttl](config-inference-2.ttl)
+
+_Eclipse Jetty HTTPs setup._
+
+A Jetty XML configuration file for running Jetty with HTTPS.
+This file will require configuration and also installing a certificate
+
+* [fuseki-jetty-https.xml](fuseki-jetty-https.xml)
diff --git a/jena-fuseki2/examples/config-1-mem.ttl b/jena-fuseki2/examples/config-1-mem.ttl
new file mode 100644
index 0000000..84fa0d4
--- /dev/null
+++ b/jena-fuseki2/examples/config-1-mem.ttl
@@ -0,0 +1,54 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+## Service description for "/dataset" with all endpoints.
+## e.g.
+##   GET /dataset/query?query=...
+##   GET /dataset/get?default (SPARQL Graph Store Protocol)
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:query ;
+        fuseki:name "sparql" 
+    ];
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query" 
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "update"
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:gsp-r ;
+        fuseki:name "get"
+    ] ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:gsp-rw ; 
+        fuseki:name "data"
+    ] ; 
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:upload ;
+        fuseki:name "upload"
+    ] ; 
+    fuseki:dataset :dataset ;
+    .
+
+# Transactional in-memory dataset.
+:dataset rdf:type ja:MemoryDataset ;
+    ## Optional load with data on start-up
+    ## ja:data "data1.trig";
+    ## ja:data "data2.trig";
+    .
+   
diff --git a/jena-fuseki2/examples/config-2-mem-old.ttl b/jena-fuseki2/examples/config-2-mem-old.ttl
new file mode 100644
index 0000000..971d345
--- /dev/null
+++ b/jena-fuseki2/examples/config-2-mem-old.ttl
@@ -0,0 +1,32 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+## Service description for "/dataset" with all endpoints.
+## Example of old, less flexible, syntax.
+
+:service rdf:type fuseki:Service;
+    fuseki:name                        "dataset";
+    fuseki:serviceQuery                "query";
+    fuseki:serviceQuery                "sparql";
+    fuseki:serviceUpdate               "update";
+    fuseki:serviceUpload               "upload" ;
+    fuseki:serviceReadWriteGraphStore  "data" ;
+    fuseki:serviceReadGraphStore       "get" ;
+    fuseki:dataset :dataset ;
+    .
+
+# Transactional in-memory dataset.
+:dataset rdf:type ja:MemoryDataset ;
+    ## Optional load with data on start-up
+    ## ja:data "data.trig";
+    .
diff --git a/jena-fuseki2/examples/config-3-dataset-endpoints.ttl b/jena-fuseki2/examples/config-3-dataset-endpoints.ttl
new file mode 100644
index 0000000..50c5462
--- /dev/null
+++ b/jena-fuseki2/examples/config-3-dataset-endpoints.ttl
@@ -0,0 +1,34 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+## Service description for "/dataset" with all endpoints.
+## Operations are on the dataset e.g. /dataset?query=
+
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+##     fuseki:endpoint [
+##         fuseki:operation fuseki:query ;
+##         fuseki:name "" # This is optional.
+##     ] ;
+    fuseki:endpoint [ fuseki:operation fuseki:query ; ] ;
+    fuseki:endpoint [ fuseki:operation fuseki:update ; ] ;
+    fuseki:endpoint [ fuseki:operation fuseki:gsp-r ;  ] ;
+    fuseki:endpoint [ fuseki:operation fuseki:gsp-rw ; ] ; 
+    fuseki:endpoint [ fuseki:operation fuseki:upload ; ] ; 
+    fuseki:dataset :dataset ;
+    .
+
+# Transactional in-memory dataset.
+:dataset rdf:type ja:MemoryDataset .
+
diff --git a/jena-fuseki2/examples/config-4-endpoint-sparql.ttl b/jena-fuseki2/examples/config-4-endpoint-sparql.ttl
new file mode 100644
index 0000000..526466c
--- /dev/null
+++ b/jena-fuseki2/examples/config-4-endpoint-sparql.ttl
@@ -0,0 +1,30 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+## Service description for "/dataset/sparql" with SPARQL query and update.
+## These can be the same fuseki:name or different.
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "sparql" ;
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "sparql" ;
+    ] ;
+    fuseki:dataset :dataset ;
+    .
+
+:dataset rdf:type ja:MemoryDataset .
diff --git a/jena-fuseki2/examples/config-inference-1.ttl b/jena-fuseki2/examples/config-inference-1.ttl
new file mode 100644
index 0000000..aae05f8
--- /dev/null
+++ b/jena-fuseki2/examples/config-inference-1.ttl
@@ -0,0 +1,43 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+# Example of a data service with only SPARQL query on an 
+# in-memory inference model.  Data is taken from a file
+# when the data service is started.
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+# Service description for "/dataset" with all endpoints.
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query" 
+    ] ;
+    fuseki:dataset :dataset ;
+    .
+
+# Dataset with only the default graph.
+:dataset rdf:type       ja:RDFDataset ;
+    ja:defaultGraph     :model_inf ;
+     .
+
+# The inference model
+:model_inf a ja:InfModel ;
+     ja:baseModel :baseModel ;
+     ja:reasoner [
+         ja:reasonerURL <http://jena.hpl.hp.com/2003/OWLFBRuleReasoner>
+     ] .
+
+# The base data.
+:baseModel a ja:MemoryModel ;
+    ja:content [ja:externalContent <file:Data/data.ttl> ] ;
+    .
diff --git a/jena-fuseki2/examples/config-inference-2.ttl b/jena-fuseki2/examples/config-inference-2.ttl
new file mode 100644
index 0000000..e7436eb
--- /dev/null
+++ b/jena-fuseki2/examples/config-inference-2.ttl
@@ -0,0 +1,52 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+# Example of a data service with SPARQL query ahgrep and update on an 
+# inference model.  Data is taken from TDB.
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+# Service description for "/dataset" with all endpoints.
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query"
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "update" 
+    ] ;
+    fuseki:dataset :dataset ;
+    .
+
+## ---------------------------------------------------------------
+
+:dataset rdf:type       ja:RDFDataset ;
+    ja:defaultGraph     :model_inf ;
+    .
+
+:model_inf a ja:InfModel ;
+     ja:baseModel :tdbGraph ;
+     ja:reasoner [
+         ja:reasonerURL <http://jena.hpl.hp.com/2003/OWLFBRuleReasoner>
+     ] .
+
+:tdbGraph rdf:type tdb2:GraphTDB2 ;
+    tdb2:dataset :tdbDataset .
+
+## Base data in TDB.
+:tdbDataset rdf:type tdb2:DatasetTDB2 ;
+    tdb2:location "DB" ;
+    # If the unionDefaultGraph is used, then the "update" service should be removed.
+    # tdb:unionDefaultGraph true ;
+    .
diff --git a/jena-fuseki2/examples/config-shacl.ttl b/jena-fuseki2/examples/config-shacl.ttl
new file mode 100644
index 0000000..288db76
--- /dev/null
+++ b/jena-fuseki2/examples/config-shacl.ttl
@@ -0,0 +1,35 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+## Service description for "/dataset" with
+## a SHACL validation service on "/dataset/shacl"
+## See https://jena.apache.org/documentation/shacl/
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    
+    fuseki:endpoint [ fuseki:operation fuseki:query ] ;
+    
+    ## SPARQL Graph Store Protocol : PUT/POST data to /dataset.
+    fuseki:endpoint [ fuseki:operation fuseki:gsp-rw ] ;
+
+## SHACL service - see https://jena.apache.org/documentation/shacl/
+    ## /dataset/shacl
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:shacl ;
+        fuseki:name "shacl"
+    ] ;
+    fuseki:dataset :dataset ;
+    .
+
+:dataset rdf:type ja:MemoryDataset .
diff --git a/jena-fuseki2/examples/config-tdb1.ttl b/jena-fuseki2/examples/config-tdb1.ttl
new file mode 100644
index 0000000..2dfe0cb
--- /dev/null
+++ b/jena-fuseki2/examples/config-tdb1.ttl
@@ -0,0 +1,51 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX tdb1:    <http://jena.hpl.hp.com/2008/tdb#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+## Service description for "/dataset"
+## with a TDB1 dataset
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:query ;
+        fuseki:name "sparql" 
+    ];
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query" 
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "update"
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:gsp-r ;
+        fuseki:name "get"
+    ] ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:gsp-rw ; 
+        fuseki:name "data"
+    ] ; 
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:upload ;
+        fuseki:name "upload"
+    ] ; 
+    fuseki:dataset :dataset ;
+    .
+
+:dataset_tdb1 rdf:type    tdb1:DatasetTDB ;
+    tdb1:location "DB1" ;
+    ## Optional - with union default for query and update WHERE matching.
+    ## tdb2:unionDefaultGraph true ;
+    .
diff --git a/jena-fuseki2/examples/config-tdb2.ttl b/jena-fuseki2/examples/config-tdb2.ttl
new file mode 100644
index 0000000..98e6f10
--- /dev/null
+++ b/jena-fuseki2/examples/config-tdb2.ttl
@@ -0,0 +1,53 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+## Service description for "/dataset"
+## with a TDB2 dataset
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:query ;
+        fuseki:name "sparql" ;
+        ## Set default-union-graph for this endpoint.
+        ja:context [ ja:cxtName "tdb:unionDefaultGraph" ; ja:cxtValue true ] ;
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query" 
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "update"
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:gsp-r ;
+        fuseki:name "get"
+    ] ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:gsp-rw ; 
+        fuseki:name "data"
+    ] ; 
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:upload ;
+        fuseki:name "upload"
+    ] ; 
+    fuseki:dataset :dataset_tdb2 ;
+    .
+
+:dataset_tdb2 rdf:type  tdb2:DatasetTDB2 ;
+    tdb2:location "DB2" ;
+    ## Optional - with union default for query and update WHERE matching.
+    ## tdb2:unionDefaultGraph true ;
+    .
diff --git a/jena-fuseki2/examples/config-text-tdb2.ttl b/jena-fuseki2/examples/config-text-tdb2.ttl
new file mode 100644
index 0000000..225a36c
--- /dev/null
+++ b/jena-fuseki2/examples/config-text-tdb2.ttl
@@ -0,0 +1,80 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
+PREFIX text:    <http://jena.apache.org/text#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+## Service description for "/dataset"
+## with a TDB2 dataset and a text index.
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:query ;
+        fuseki:name "sparql" 
+    ];
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query" 
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "update"
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:gsp-r ;
+        fuseki:name "get"
+    ] ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:gsp-rw ; 
+        fuseki:name "data"
+    ] ; 
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:upload ;
+        fuseki:name "upload"
+    ] ; 
+    fuseki:dataset :text_dataset ;
+    .
+
+## ---------------------------------------------------------------
+## Confiure a dataset with a text index.
+## See
+##   https://jena.apache.org/documentation/query/text-query.html
+## for details
+
+:text_dataset rdf:type     text:TextDataset ;
+    text:dataset   :base_dataset ;
+    text:index     :indexLucene ;
+    .
+
+:base_dataset rdf:type      tdb2:DatasetTDB2 ;
+    tdb2:location "DB2"
+    .
+
+:indexLucene a text:TextIndexLucene ;
+    # File location of index
+    text:directory <file:Lucene> ;
+    text:entityMap :entMap ;
+    .
+
+<#entMap> a text:EntityMap ;
+    text:entityField      "uri" ;
+    ## Must be defined in the text:map
+    text:defaultField     "text" ;
+    ## Enable deleting of text index entries.
+    text:uidField         "uid" ;
+    text:map (
+         # rdfs:label            
+         [ text:field "text" ; text:predicate rdfs:label ]
+         )
+    .
diff --git a/jena-fuseki2/examples/config-timeout-dataset.ttl b/jena-fuseki2/examples/config-timeout-dataset.ttl
new file mode 100644
index 0000000..846a3e0
--- /dev/null
+++ b/jena-fuseki2/examples/config-timeout-dataset.ttl
@@ -0,0 +1,51 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+## Service description for "/dataset"
+## with a TDB2 dataset with a query timeout.
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:query ;
+        fuseki:name "sparql" 
+    ];
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query" 
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "update"
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:gsp-r ;
+        fuseki:name "get"
+    ] ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:gsp-rw ; 
+        fuseki:name "data"
+    ] ; 
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:upload ;
+        fuseki:name "upload"
+    ] ; 
+    fuseki:dataset :dataset_tdb2 ;
+    .
+
+:dataset_tdb2 rdf:type  tdb2:DatasetTDB2 ;
+    ## Set the timeout for the dataset
+    ja:context [ ja:cxtName "arq:queryTimeout" ;  ja:cxtValue "10000,60000" ] ;
+    tdb2:location "DB2" ;
+    .
diff --git a/jena-fuseki2/examples/config-timeout-endpoint.ttl b/jena-fuseki2/examples/config-timeout-endpoint.ttl
new file mode 100644
index 0000000..094352e
--- /dev/null
+++ b/jena-fuseki2/examples/config-timeout-endpoint.ttl
@@ -0,0 +1,52 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+## Service description for "/dataset"
+## with a TDB2 dataset with a timeout on one endpoint.
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [
+        ja:context [ ja:cxtName "arq:queryTimeout" ;  ja:cxtValue "10000,60000" ] ;
+        fuseki:operation fuseki:query ;
+        fuseki:name "sparql" 
+    ];
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query" 
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "update"
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:gsp-r ;
+        fuseki:name "get"
+    ] ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:gsp-rw ; 
+        fuseki:name "data"
+    ] ; 
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:upload ;
+        fuseki:name "upload"
+    ] ; 
+    fuseki:dataset :dataset_tdb2 ;
+    .
+
+:dataset_tdb2 rdf:type  tdb2:DatasetTDB2 ;
+    tdb2:location "DB2" ;
+    ## Optional - with union default for query and update WHERE matching.
+    ## tdb2:unionDefaultGraph true ;
+    .
diff --git a/jena-fuseki2/examples/config-timeout-server.ttl b/jena-fuseki2/examples/config-timeout-server.ttl
new file mode 100644
index 0000000..43aefc0
--- /dev/null
+++ b/jena-fuseki2/examples/config-timeout-server.ttl
@@ -0,0 +1,53 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
+
+## Server-wide timeout (all datasets).
+
+[] rdf:type fuseki:Server ;
+    # Format 1: "10000" -- 10 second timeout
+    # Format 2: "10000,60000" -- 10s timeout to first result,
+    #   then 60s timeout to for rest of query.
+    # See javadoc for ARQ.queryTimeout
+    ja:context [ ja:cxtName "arq:queryTimeout" ;  ja:cxtValue "10000" ] ;
+    fuseki:services (
+       :service
+    ) .
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:query ;
+        fuseki:name "sparql" 
+    ];
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query" 
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "update"
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:gsp-r ;
+        fuseki:name "get"
+    ] ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:gsp-rw ; 
+        fuseki:name "data"
+    ] ; 
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:upload ;
+        fuseki:name "upload"
+    ] ; 
+    fuseki:dataset :dataset_tdb2 ;
+    .
+
+:dataset_tdb2 rdf:type  tdb2:DatasetTDB2 ;
+    tdb2:location "DB2"
+    .
diff --git a/jena-fuseki2/examples/fuseki-in-mem-txn.ttl b/jena-fuseki2/examples/fuseki-in-mem-txn.ttl
deleted file mode 100644
index 1229e98..0000000
--- a/jena-fuseki2/examples/fuseki-in-mem-txn.ttl
+++ /dev/null
@@ -1,24 +0,0 @@
-## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-
-@prefix :        <#> .
-@prefix fuseki:  <http://jena.apache.org/fuseki#> .
-@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
-@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix ja:      <http://jena.hpl.hp.com/2005/11/Assembler#> .
-@prefix tdb:     <http://jena.hpl.hp.com/2008/tdb#> .
-
-<#serviceInMemory> rdf:type fuseki:Service;
-    rdfs:label                   "In-memory, transactional dataset.";
-    fuseki:name                  "ds";
-    fuseki:serviceQuery          "query";
-    fuseki:serviceQuery          "sparql";
-    fuseki:serviceUpdate         "update";
-    fuseki:serviceUpload         "upload" ;
-    fuseki:serviceReadWriteGraphStore "data" ;
-    fuseki:serviceReadGraphStore "get" ;
-    fuseki:dataset <#dataset> ;
-.
-
-<#dataset> rdf:type ja:MemoryDataset;
-   ja:data "data.trig";
-.
diff --git a/jena-fuseki2/examples/fuseki-jetty-https.xml b/jena-fuseki2/examples/fuseki-jetty-https.xml
index d922e19..d9cb774 100644
--- a/jena-fuseki2/examples/fuseki-jetty-https.xml
+++ b/jena-fuseki2/examples/fuseki-jetty-https.xml
@@ -269,8 +269,8 @@
   <Call name="addIfAbsentConnectionFactory">
     <Arg>
       <New class="org.eclipse.jetty.server.SslConnectionFactory">
-	<Arg name="next">http/1.1</Arg>
-	<Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg>
+        <Arg name="next">http/1.1</Arg>
+        <Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg>
       </New>
     </Arg>
   </Call>
@@ -278,11 +278,10 @@
   <Call name="addConnectionFactory">
     <Arg>
       <New class="org.eclipse.jetty.server.HttpConnectionFactory">
-	<Arg name="config"><Ref refid="sslHttpConfig" /></Arg>
+     	<Arg name="config"><Ref refid="sslHttpConfig" /></Arg>
       </New>
     </Arg>
   </Call>
 </Ref>
 
-
 </Configure>
diff --git a/jena-fuseki2/examples/fuseki-tdb2.ttl b/jena-fuseki2/examples/fuseki-tdb2.ttl
deleted file mode 100644
index 8000156..0000000
--- a/jena-fuseki2/examples/fuseki-tdb2.ttl
+++ /dev/null
@@ -1,33 +0,0 @@
-## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-
-## Example of a Fuseki server configuration file with a TDB2 dataset
-
-PREFIX :        <#>
-PREFIX fuseki:  <http://jena.apache.org/fuseki#>
-PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
-PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
-PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
-
-[] rdf:type fuseki:Server ;
-   fuseki:services (
-     <#service_tdb2>
-   ) .
-
-<#service_tdb2> rdf:type fuseki:Service ;
-    rdfs:label                      "TDB2 Service (RW)" ;
-    fuseki:name                     "tdb2-database" ;
-    fuseki:serviceQuery             "query" ;
-    fuseki:serviceQuery             "sparql" ;
-    fuseki:serviceUpdate            "update" ;
-    fuseki:serviceUpload            "upload" ;
-    fuseki:serviceReadWriteGraphStore      "data" ;
-    fuseki:serviceReadGraphStore    "get" ;
-    fuseki:dataset           <#tdb_dataset_readwrite> ;
-    .
-
-<#tdb_dataset_readwrite> rdf:type      tdb2:DatasetTDB2 ;
-    tdb2:location "TDB2" ;
-    ## tdb2:unionDefaultGraph true ;
-    .
- 
\ No newline at end of file
diff --git a/jena-fuseki2/examples/service-inference-1.ttl b/jena-fuseki2/examples/service-inference-1.ttl
deleted file mode 100644
index 5fe894b..0000000
--- a/jena-fuseki2/examples/service-inference-1.ttl
+++ /dev/null
@@ -1,39 +0,0 @@
-## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-
-# Example of a data service with only SPARQL query on an 
-# in-memory inference model.  Data is taken from a file
-# when the data service is started.
-
-@prefix :        <#> .
-@prefix fuseki:  <http://jena.apache.org/fuseki#> .
-@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
-
-@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix tdb:     <http://jena.hpl.hp.com/2008/tdb#> .
-@prefix ja:      <http://jena.hpl.hp.com/2005/11/Assembler#> .
-
-## Service with only SPARQL query on an inference model.
-## Inference model bbase data in TDB.
-
-<#service1>  rdf:type fuseki:Service ;
-    fuseki:name              "inf-mem" ;
-    fuseki:serviceQuery      "sparql" ;          # SPARQL query service
-    fuseki:dataset           <#dataset> ;
-    .
-
-# Dataset with only the default graph.
-<#dataset> rdf:type       ja:RDFDataset ;
-    ja:defaultGraph       <#model_inf> ;
-     .
-
-# The inference model
-<#model_inf> a ja:InfModel ;
-     ja:baseModel <#baseModel> ;
-     ja:reasoner [
-         ja:reasonerURL <http://jena.hpl.hp.com/2003/OWLFBRuleReasoner>
-     ] .
-
-# The base data.
-<#baseModel> a ja:MemoryModel ;
-    ja:content [ja:externalContent <file:Data/data.ttl> ] ;
-    .
diff --git a/jena-fuseki2/examples/service-inference-2.ttl b/jena-fuseki2/examples/service-inference-2.ttl
deleted file mode 100644
index b036fa6..0000000
--- a/jena-fuseki2/examples/service-inference-2.ttl
+++ /dev/null
@@ -1,43 +0,0 @@
-## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-
-@prefix :        <#> .
-@prefix fuseki:  <http://jena.apache.org/fuseki#> .
-@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
-
-@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix tdb:     <http://jena.hpl.hp.com/2008/tdb#> .
-@prefix ja:      <http://jena.hpl.hp.com/2005/11/Assembler#> .
-
-# Example of a data service with SPARQL query abnd update on an 
-# inference model.  Data is taken from TDB.
-
-## ---------------------------------------------------------------
-## Service with only SPARQL query on an inference model.
-## Inference model base data is in TDB.
-
-<#service2>  rdf:type fuseki:Service ;
-    fuseki:name              "inf" ;             # http://host/inf
-    fuseki:serviceQuery      "sparql" ;          # SPARQL query service
-    fuseki:serviceUpdate     "update" ;
-    fuseki:dataset           <#dataset> ;
-    .
-
-<#dataset> rdf:type       ja:RDFDataset ;
-    ja:defaultGraph       <#model_inf> ;
-     .
-
-<#model_inf> a ja:InfModel ;
-     ja:baseModel <#tdbGraph> ;
-     ja:reasoner [
-         ja:reasonerURL <http://jena.hpl.hp.com/2003/OWLFBRuleReasoner>
-     ] .
-
-## Base data in TDB.
-<#tdbDataset> rdf:type tdb:DatasetTDB ;
-    tdb:location "DB" ;
-    # If the unionDefaultGraph is used, then the "update" service should be removed.
-    # tdb:unionDefaultGraph true ;
-    .
-
-<#tdbGraph> rdf:type tdb:GraphTDB ;
-    tdb:dataset <#tdbDataset> .
diff --git a/jena-fuseki2/examples/service-tdb1-A.ttl b/jena-fuseki2/examples/service-tdb1-A.ttl
deleted file mode 100644
index 8f0c34a..0000000
--- a/jena-fuseki2/examples/service-tdb1-A.ttl
+++ /dev/null
@@ -1,29 +0,0 @@
-## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-
-## Example of a TDB dataset published using Fuseki
-## Persistent storage.
-## read-only
-
-@prefix :        <#> .
-@prefix fuseki:  <http://jena.apache.org/fuseki#> .
-@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
-@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix tdb:     <http://jena.hpl.hp.com/2008/tdb#> .
-@prefix ja:      <http://jena.hpl.hp.com/2005/11/Assembler#> .
-
-## Read-only TDB dataset (only read services enabled).
-
-<#service_tdb_read_only> rdf:type fuseki:Service ;
-    rdfs:label                      "TDB Service (R)" ;
-    fuseki:name                     "ds" ;
-    fuseki:serviceQuery             "query" ;
-    fuseki:serviceQuery             "sparql" ;
-    fuseki:serviceReadGraphStore    "data" ;
-    fuseki:serviceReadGraphStore    "get" ;
-    fuseki:dataset           <#tdb_dataset_read> ;
-    .
-
-<#tdb_dataset_read> rdf:type      tdb:DatasetTDB ;
-    tdb:location "DB1" ;
-    tdb:unionDefaultGraph true ;
-    .
diff --git a/jena-fuseki2/examples/service-tdb1-B.ttl b/jena-fuseki2/examples/service-tdb1-B.ttl
deleted file mode 100644
index c85963f..0000000
--- a/jena-fuseki2/examples/service-tdb1-B.ttl
+++ /dev/null
@@ -1,33 +0,0 @@
-## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-
-## Example of a TDB dataset published using Fuseki
-## Persistent storage.  
-## All services enabled.
-
-@prefix :        <#> .
-@prefix fuseki:  <http://jena.apache.org/fuseki#> .
-@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
-@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix tdb:     <http://jena.hpl.hp.com/2008/tdb#> .
-@prefix ja:      <http://jena.hpl.hp.com/2005/11/Assembler#> .
-
-<#service_tdb_all> rdf:type fuseki:Service ;
-    rdfs:label                      "TDB Service (RW)" ;
-    fuseki:name                     "data" ;
-    fuseki:serviceQuery             "query" ;
-    fuseki:serviceQuery             "sparql" ;
-    fuseki:serviceUpdate            "update" ;
-    fuseki:serviceUpload            "upload" ;
-    fuseki:serviceReadWriteGraphStore      "data" ;
-    # A separate read-only graph store endpoint:
-    fuseki:serviceReadGraphStore       "get" ;
-    fuseki:dataset           <#tdb_dataset_readwrite> ;
-    .
-
-<#tdb_dataset_readwrite> rdf:type      tdb:DatasetTDB ;
-    tdb:location "DB2" ;
-##     # Query timeout on this dataset (milliseconds)
-##     ja:context [ ja:cxtName "arq:queryTimeout" ;  ja:cxtValue "1000" ] ;
-##     # Default graph for query is the (read-only) union of all named graphs.
-       tdb:unionDefaultGraph true ;
-     .
diff --git a/jena-fuseki2/examples/service-tdb1-mem.ttl b/jena-fuseki2/examples/service-tdb1-mem.ttl
deleted file mode 100644
index 82a3fc2..0000000
--- a/jena-fuseki2/examples/service-tdb1-mem.ttl
+++ /dev/null
@@ -1,32 +0,0 @@
-## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-
-## An in-memory TDB datasets - for testing.
-
-@prefix :        <#> .
-@prefix fuseki:  <http://jena.apache.org/fuseki#> .
-@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
-
-@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix tdb:     <http://jena.hpl.hp.com/2008/tdb#> .
-@prefix ja:      <http://jena.hpl.hp.com/2005/11/Assembler#> .
-
-<#service_tdb> rdf:type fuseki:Service ;
-    rdfs:label                      "TDB(mem) Service" ;
-    fuseki:name                     "MEM" ;
-    fuseki:serviceQuery             "query" ;
-    fuseki:serviceQuery             "sparql" ;
-    fuseki:serviceUpdate            "update" ;
-    fuseki:serviceUpload            "upload" ;
-    fuseki:serviceReadWriteGraphStore      "data" ;
-    # A separate read-only graph store endpoint:
-    fuseki:serviceReadGraphStore       "get" ;
-    fuseki:dataset           <#tdb_dataset> ;
-    .
-
-<#tdb_dataset> rdf:type      tdb:DatasetTDB ;
-    tdb:location "--mem--" ;
-    ## Query timeout on this dataset (milliseconds)
-    ja:context [ ja:cxtName "arq:queryTimeout" ;  ja:cxtValue "1000" ] ;
-    ## Default graph for query is the (read-only) union of all named graphs.
-    tdb:unionDefaultGraph true ;
-   .
diff --git a/jena-fuseki2/examples/service-tdb2-A.ttl b/jena-fuseki2/examples/service-tdb2-A.ttl
deleted file mode 100644
index 89b87ff..0000000
--- a/jena-fuseki2/examples/service-tdb2-A.ttl
+++ /dev/null
@@ -1,28 +0,0 @@
-## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-
-## Example of a service configuration file with a TDB2 dataset
-
-PREFIX :        <#>
-PREFIX fuseki:  <http://jena.apache.org/fuseki#>
-PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
-PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
-PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
-
-<#service_tdb2> rdf:type fuseki:Service ;
-    rdfs:label                      "TDB2 Service (RW)" ;
-    fuseki:name                     "tdb2-database" ;
-    fuseki:serviceQuery             "query" ;
-    fuseki:serviceQuery             "sparql" ;
-    fuseki:serviceUpdate            "update" ;
-    fuseki:serviceUpload            "upload" ;
-    fuseki:serviceReadWriteGraphStore      "data" ;
-    fuseki:serviceReadGraphStore    "get" ;
-    fuseki:dataset           <#tdb_dataset_readwrite> ;
-    .
-
-<#tdb_dataset_readwrite> rdf:type      tdb2:DatasetTDB2 ;
-    tdb2:location "TDB2" ;
-    ## tdb2:unionDefaultGraph true ;
-    .
- 
\ No newline at end of file
diff --git a/jena-fuseki2/examples/shacl-example.ttl b/jena-fuseki2/examples/shacl-example.ttl
deleted file mode 100644
index cc8400a..0000000
--- a/jena-fuseki2/examples/shacl-example.ttl
+++ /dev/null
@@ -1,17 +0,0 @@
-## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-
-PREFIX :        <#>
-PREFIX fuseki:  <http://jena.apache.org/fuseki#>
-PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
-PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
-
-<#serviceInMemoryShacl> rdf:type fuseki:Service;
-    rdfs:label                   "Dataset with SHACL validation";
-    fuseki:name                  "ds";
-    fuseki:serviceReadWriteGraphStore "" ;
-    fuseki:serviceShacl          "shacl" ;
-    fuseki:dataset <#dataset> ;
-.
-
-<#dataset> rdf:type ja:MemoryDataset .
diff --git a/jena-fuseki2/examples/tdb2-select-graphs-alt.ttl b/jena-fuseki2/examples/tdb2-select-graphs-alt.ttl
index 8f3a165..9a1746c 100644
--- a/jena-fuseki2/examples/tdb2-select-graphs-alt.ttl
+++ b/jena-fuseki2/examples/tdb2-select-graphs-alt.ttl
@@ -3,53 +3,72 @@
 PREFIX :        <#>
 PREFIX fuseki:  <http://jena.apache.org/fuseki#>
 PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-
 PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
-PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
 PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
 
-<#service_tdb> rdf:type fuseki:Service ;
-    rdfs:label                      "Selection of TDB2 graphs" ;
-    fuseki:name                     "ds2" ;
-    fuseki:serviceQuery             "query" ;
-    fuseki:serviceQuery             "sparql" ;
-    fuseki:serviceUpdate            "update" ;
-    fuseki:serviceUpload            "upload" ;
-    fuseki:serviceReadWriteGraphStore      "data" ;
-    # A separate read-only graph store endpoint:
-    fuseki:serviceReadGraphStore       "get" ;
-    fuseki:dataset           <#dataset> ;
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:query ;
+        fuseki:name "sparql" 
+    ];
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query" 
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "update"
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:gsp-r ;
+        fuseki:name "get"
+    ] ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:gsp-rw ; 
+        fuseki:name "data"
+    ] ; 
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:upload ;
+        fuseki:name "upload"
+    ] ; 
+    fuseki:dataset :dataset ;
     .
 
+## Datasets with some graphs from a TDB2 dataset.
+## Note: Prefer using "GRAPH" in a SPARQL query where possible.
 
-<#dataset> rdf:type      ja:RDFDataset ;
-    ja:defaultGraph <#graph> ;
+:dataset rdf:type      ja:RDFDataset ;
+    ja:defaultGraph :graph ;
     ja:namedGraph [
           ja:graphName <https://example/ng1> ;
-          ja:graph <#graph2>
+          ja:graph :graph2
       ];
     ja:namedGraph [
           ja:graphName <https://example/ng2> ;
-          ja:graph <#graph3>
+          ja:graph :graph3
       ];
     . 
 
 ## Graphs out of DB2.
+## If the location is the same, it will be the same dataset.
 
-<#graph> rdf:type tdb2:GraphTDB2 ;
+:graph rdf:type tdb2:GraphTDB2 ;
     tdb2:graphName "urn:x-arq:UnionGraph" ;
-    tdb2:dataset <#DB2> ;
+    tdb2:location "DB2" ;
     .
 
-<#graph2>  rdf:type tdb2:GraphTDB2 ;
+:graph2  rdf:type tdb2:GraphTDB2 ;
     tdb2:graphName "https://example/ng1" ;
-    tdb2:dataset <#DB2> ;
+    tdb2:location "DB2" ;
     .
-<#graph3>  rdf:type tdb2:GraphTDB2 ;
+:graph3  rdf:type tdb2:GraphTDB2 ;
     tdb2:graphName "https://example/ng" ;
-    tdb2:dataset <#DB2> ;
-    .
-
-<#DB2>  rdf:type tdb2:DatasetTDB2 ;
     tdb2:location "DB2" ;
     .
diff --git a/jena-fuseki2/examples/tdb2-select-graphs.ttl b/jena-fuseki2/examples/tdb2-select-graphs.ttl
index 1e66b6d..3f3bae2 100644
--- a/jena-fuseki2/examples/tdb2-select-graphs.ttl
+++ b/jena-fuseki2/examples/tdb2-select-graphs.ttl
@@ -3,49 +3,77 @@
 PREFIX :        <#>
 PREFIX fuseki:  <http://jena.apache.org/fuseki#>
 PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-
 PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
-PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
 PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
 
-<#service_tdb> rdf:type fuseki:Service ;
-    rdfs:label                      "Selection of TDB2 graphs" ;
-    fuseki:name                     "ds2" ;
-    fuseki:serviceQuery             "query" ;
-    fuseki:serviceQuery             "sparql" ;
-    fuseki:serviceUpdate            "update" ;
-    fuseki:serviceUpload            "upload" ;
-    fuseki:serviceReadWriteGraphStore      "data" ;
-    # A separate read-only graph store endpoint:
-    fuseki:serviceReadGraphStore       "get" ;
-    fuseki:dataset           <#dataset> ;
+[] rdf:type fuseki:Server ;
+   fuseki:services (
+     :service
+   ) .
+
+:service rdf:type fuseki:Service ;
+    fuseki:name "dataset" ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:query ;
+        fuseki:name "sparql" 
+    ];
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ;
+        fuseki:name "query" 
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:update ;
+        fuseki:name "update"
+    ] ;
+    fuseki:endpoint [
+        fuseki:operation fuseki:gsp-r ;
+        fuseki:name "get"
+    ] ;
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:gsp-rw ; 
+        fuseki:name "data"
+    ] ; 
+    fuseki:endpoint [ 
+        fuseki:operation fuseki:upload ;
+        fuseki:name "upload"
+    ] ; 
+    fuseki:dataset :dataset ;
     .
 
+## Datasets with some graphs from a TDB2 dataset.
+## Note: Prefer using "GRAPH" in a SPARQL query where possible.
 
-<#dataset> rdf:type      ja:RDFDataset ;
-    ja:defaultGraph <#graph> ;
+:dataset rdf:type      ja:RDFDataset ;
+    ja:defaultGraph :graph ;
     ja:namedGraph [
           ja:graphName <https://example/ng1> ;
-          ja:graph <#graph2>
+          ja:graph :graph2
       ];
     ja:namedGraph [
           ja:graphName <https://example/ng2> ;
-          ja:graph <#graph3>
+          ja:graph :graph3
       ];
     . 
 
 ## Graphs out of DB2.
 
-<#graph> rdf:type tdb2:GraphTDB2 ;
+:graph rdf:type tdb2:GraphTDB2 ;
     tdb2:graphName "urn:x-arq:UnionGraph" ;
-    tdb2:location "DB2" ;
+    tdb2:dataset :dataset_tdb2 ;
     .
 
-<#graph2>  rdf:type tdb2:GraphTDB2 ;
+:graph2  rdf:type tdb2:GraphTDB2 ;
     tdb2:graphName "https://example/ng1" ;
-    tdb2:location "DB2" ;
+    tdb2:dataset :dataset_tdb2 ;
     .
-<#graph3>  rdf:type tdb2:GraphTDB2 ;
+:graph3  rdf:type tdb2:GraphTDB2 ;
     tdb2:graphName "https://example/ng" ;
+    tdb2:dataset :dataset_tdb2 ;
+    .
+
+## The database
+
+:dataset_tdb2  rdf:type tdb2:DatasetTDB2 ;
     tdb2:location "DB2" ;
     .
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
index d822714..f03eca1 100644
--- 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
@@ -65,7 +65,8 @@
     public void execAny(String methodName, HttpAction action) {
         if ( label == null )
             ServletOps.errorBadRequest("Not supported");
-        ServletOps.errorBadRequest(label+" : not supported");
+        else
+            ServletOps.errorBadRequest(label+" : not supported");
         throw new InternalErrorException("AccessCtl_AllowGET: "+ "didn't reject request");
     }
 
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DatasetDescriptionMap.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DatasetDescriptionMap.java
index c814a8f..647aa94 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DatasetDescriptionMap.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DatasetDescriptionMap.java
@@ -34,17 +34,15 @@
  * same dataset.
  */
 public class DatasetDescriptionMap  {
-	
+
 	private Map<Resource, Dataset> map = new HashMap<>();
-	
+
 	public DatasetDescriptionMap() {}
-	
+
     public void register(Resource node, Dataset ds) {
         Dataset dsCurrent = map.get(node);
-        if ( dsCurrent != null ) {
-            if ( ! dsCurrent.equals(ds) )
-                Log.warn(this.getClass(), "Replacing registered dataset for "+node);
-        }
+        if ( dsCurrent != null && ! dsCurrent.equals(ds) )
+            Log.warn(this.getClass(), "Replacing registered dataset for "+node);
         map.put(node, ds);
     }
 
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 ba3b956..3988818 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
@@ -46,6 +46,7 @@
 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.auth.AuthPolicyList;
 import org.apache.jena.fuseki.server.*;
 import org.apache.jena.fuseki.servlets.ActionService;
 import org.apache.jena.graph.Node;
@@ -455,17 +456,62 @@
         accEndpointOldStyle(endpoints1, Operation.GSP_R,    fusekiService,  pServiceReadGraphStoreEP);
         accEndpointOldStyle(endpoints1, Operation.GSP_RW,   fusekiService,  pServiceReadWriteGraphStoreEP);
 
+        // ---- Legacy for old style: a request would also try the dataset (i.e. no endpoint name).
+        // If "sparql" then allow /dataset?query=
+        // Instead, for old style declarations, add new endpoints to put on the dataset
+        // Only complication is that authorization is the AND (all say "yes") of named service authorization.
+        {
+            Collection<Endpoint> endpointsCompat = oldStyleCompat(dataService, endpoints1);
+            endpointsCompat.forEach(dataService::addEndpoint);
+        }
+        // Explicit definition overrides implied by legacy compatibility.
+        // Should not happen.
+        endpoints1.forEach(dataService::addEndpoint);
+
         // New (2019) style
         // fuseki:endpoint [ fuseki:operation fuseki:query ; fuseki:name "" ; fuseki:allowedUsers (....) ] ;
         //   and more.
-        accFusekiEndpoints(endpoints2, fusekiService, dsDescMap);
 
-        endpoints1.forEach(dataService::addEndpoint);
+        accFusekiEndpoints(endpoints2, fusekiService, dsDescMap);
         // This will overwrite old style entries of the same fuseki:name.
         endpoints2.forEach(dataService::addEndpoint);
+
         return dataService;
     }
 
+    /**
+     *  Old style compatibility.
+     *  For each endpoint in "endpoints1", ensure there is an endpoint on the dataset (endpoint name "") itself.
+     *  Combine the authentication as "AND" of named endpoints authentication.
+     */
+    private static Collection<Endpoint> oldStyleCompat(DataService dataService, Set<Endpoint> endpoints1) {
+        Map<Operation, Endpoint> endpoints3 = new HashMap<>();
+        endpoints1.forEach(ep->{
+           Operation operation = ep.getOperation();
+           AuthPolicy auth = ep.getAuthPolicy();
+
+           if ( ! StringUtils.isEmpty(ep.getName()) ) {
+               if ( endpoints3.containsKey(operation) ) {
+                   Endpoint ep1 = endpoints3.get(operation);
+                   // Accumulate Authorization.
+                   auth = AuthPolicyList.merge(ep1.getAuthPolicy(), auth);
+                   Endpoint ep2 = Endpoint.create(ep.getOperation(), "", auth);
+                   endpoints3.put(operation, ep2);
+               } else {
+                   Endpoint ep2 = Endpoint.create(operation, "", auth);
+                   endpoints3.put(operation, ep2);
+               }
+           }
+        });
+        // Now, after making all legacy endpoints, remove any that are explicit defined in endpoints1.
+        // Given the small numbers involved, it is easier to do it this way than
+        // additional logic in the first pass over endpoints1.
+        endpoints1.stream()
+            .filter(ep->StringUtils.isEmpty(ep.getName()))
+            .forEach(ep->endpoints3.remove(ep.getOperation()));
+        return endpoints3.values();
+    }
+
     /** Find and parse {@code fuseki:endpoint} descriptions. */
     private
     static void accFusekiEndpoints(Set<Endpoint> endpoints, Resource fusekiService, DatasetDescriptionMap dsDescMap) {
@@ -540,7 +586,6 @@
             epName = epNameR.asLiteral().getLexicalForm();
         }
 
-
         Context cxt = parseContext(endpoint);
 
         // Per-endpoint context.
@@ -587,11 +632,11 @@
             RDFNode ep = soln.get("ep");
             String endpointName = null;
             if ( ep.isLiteral() )
+                // fuseki:serviceQuery "sparql"
                 endpointName = soln.getLiteral("ep").getLexicalForm();
             else if ( ep.isResource() ) {
                 Resource r = (Resource)ep;
                 try {
-                    // Look for possible:
                     // [ fuseki:name ""; fuseki:allowedUsers ( "" "" ) ]
                     endpointName = r.getProperty(FusekiVocab.pEndpointName).getString();
                     List<RDFNode> x = GraphUtils.multiValue(r, FusekiVocab.pAllowedUsers);
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiExt.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiExt.java
index 9df6b02..79188d9 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiExt.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiExt.java
@@ -64,6 +64,12 @@
 
     /** Make a new operation available. */
     public static void registerOperation(Operation op, ActionService handler) {
+        // No Content-type registration 
         OperationRegistry.get().register(op, null, handler);
     }
+    
+    /** Remove an operation. */
+    public static void unregisterOperation(Operation op, ActionService handler) {
+        OperationRegistry.get().unregister(op);
+    }
 }
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 fd6bc03..b4b880e 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
@@ -20,66 +20,43 @@
 
 import static java.lang.String.format;
 
-import java.io.*;
+import java.io.IOException;
 
 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;
 
-/** The usual Fuseki error handler.
- *  Outputs a plain text message.
+/**
+ * Fuseki error handler (used with ServletAPI HttpServletResponse.sendError).
+ * Typically ServletOps.responseSendError is used which directly send the error and a message. 
  */
-
 public class FusekiErrorHandler extends ErrorHandler
 {
+    // Only used if ServletOps.responseSendError calls Servlet API response.sendError
+    // or a non-Fuseki error occurs.
     public FusekiErrorHandler() {}
 
     @Override
     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()) )
+        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);
-        try ( Writer writer = IO.asUTF8(bytes) ) {
-            String reason = (response instanceof Response) ? ((Response)response).getReason() : null;
-            handleErrorPage(request, writer, response.getStatus(), reason);
-            writer.flush();
-            response.setContentLength(bytes.size());
-            response.getOutputStream().write(bytes.toByteArray());
-        }
-    }
-
-    @Override
-    protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) throws IOException {
+        int code = response.getStatus();
+        String message = (response instanceof Response) ? ((Response)response).getReason() : HttpSC.getMessage(code);
         if ( message == null )
             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) {
-            writer.write("\n");
-            StringWriter sw = new StringWriter();
-            PrintWriter pw = new PrintWriter(sw);
-            th.printStackTrace(pw);
-            pw.flush();
-            writer.write(sw.getBuffer().toString());
-            writer.write("\n");
-            th = th.getCause();
-        }
+        String msg = format("Error %d: %s\n", code, message);
+        ServletOps.writeMessagePlainText(response, msg);
     }
 }
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
deleted file mode 100644
index 7210afc..0000000
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler1.java
+++ /dev/null
@@ -1,56 +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.jetty;
-
-import static java.lang.String.format;
-
-import java.io.IOException;
-
-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;
-
-/** 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 {
-        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));
-    }
-}
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 09fd560..b8b1bc0 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
@@ -27,6 +27,10 @@
 import org.apache.jena.fuseki.FusekiException;
 import org.apache.jena.fuseki.metrics.FusekiRequestsMetrics;
 
+/**
+ * Registry of (dataset name, {@link DataAccessPoint}).
+ * In addition, registered {@link DataAccessPoint} are added to the metrics.
+ */
 public class DataAccessPointRegistry extends Registry<String, DataAccessPoint>
 {
     private MeterRegistry meterRegistry;
@@ -60,6 +64,11 @@
     }
 
     // Debugging
+    public void print() {
+        print(null);
+    }
+
+    // Debugging
     public void print(String string) {
         System.out.flush();
         if ( string == null )
@@ -69,7 +78,7 @@
             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("    %-10s @ \"%s\"\n", op, ep.getName());
                 });
             });
         });
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Dispatcher.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Dispatcher.java
index 285d9bf..5d3cd21 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Dispatcher.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Dispatcher.java
@@ -26,13 +26,10 @@
 import static org.apache.jena.fuseki.server.Operation.Update;
 import static org.apache.jena.fuseki.servlets.ActionExecLib.allocHttpAction;
 
-import java.util.Collection;
-import java.util.List;
-
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.jena.atlas.lib.InternalErrorException;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.jena.fuseki.Fuseki;
 import org.apache.jena.fuseki.auth.Auth;
 import org.apache.jena.fuseki.servlets.*;
@@ -70,12 +67,10 @@
      *
      * This function is called by {@link FusekiFilter#doFilter}.
      *
-     * @param request
-     *            HttpServletRequest
-     * @param response
-     *            HttpServletResponse
-     * @return Returns {@code true} if the request has been handled, else false (no
-     *         response sent).
+     * Returns {@code true} if the request has been handled, including an error response sent,
+     * and returns false (no error or response sent) if the request has not been handled.
+     *
+     * This function does not throw exceptions.
      */
     public static boolean dispatch(HttpServletRequest request, HttpServletResponse response) {
         // Path component of the URI, without context path
@@ -101,6 +96,14 @@
         return process(dap, request, response);
     }
 
+    /**
+     * Map request to operation name.
+     * Returns the service name (the part after the "/" of the dataset part) or "".
+     */
+    private static String mapRequestToEndpointName(HttpAction action, DataAccessPoint dataAccessPoint) {
+        return ActionLib.mapRequestToEndpointName(action, dataAccessPoint);
+    }
+
     /** Set up and handle a HTTP request for a dataset. */
     private static boolean process(DataAccessPoint dap, HttpServletRequest request, HttpServletResponse response) {
         HttpAction action = allocHttpAction(dap, Fuseki.actionLog, ActionCategory.ACTION, request, response);
@@ -111,7 +114,7 @@
      * Determine and call the {@link ActionProcessor} to handle this
      * {@link HttpAction}, including access control at the dataset and service levels.
      */
-    public static boolean dispatchAction(HttpAction action) {
+    private static boolean dispatchAction(HttpAction action) {
         return ActionExecLib.execAction(action, ()->chooseProcessor(action));
     }
 
@@ -128,9 +131,8 @@
      * <li> Endpoint to Operation (endpoint carries Operation).
      * <li> target(action, operation) -> ActionProcess.
      *
-     * @param action
-     * @return ActionProcessor or null if the request URI does not name a service or the dataset.
-     *
+     * @return ActionProcessor or null if the request URI can not be dealt with.
+     * @throws ActionErrorException for dispatch errors
      */
     private static ActionProcessor chooseProcessor(HttpAction action) {
         // "return null" indicates that processing failed to find a ActionProcessor
@@ -144,19 +146,16 @@
 
         // ---- Determine Endpoint.
         String endpointName = mapRequestToEndpointName(action, dataAccessPoint);
+        // Main step of choosing the endpoint for the dispatch of the request.
+        // An endpoint is a (name, operation).
+        // There may be multiple operations for an endpointName of this data service.
 
         Endpoint endpoint = chooseEndpoint(action, dataService, endpointName);
-        if ( endpoint == null ) {
-            if ( isEmpty(endpointName) )
-                ServletOps.errorBadRequest("No operation for request: "+action.getActionURI());
-            else {
-                // No dispatch - the filter passes these through if the ActionProcessor is null.
-                return null;
-                // If this is used, resources (servlets, sttaic files) under "/dataset/" are not accessible.
-                //ServletOps.errorNotFound("No endpoint: "+action.getActionURI());
-            }
+        if ( endpoint == null )
+            // Includes named service, no such endpoint.
+            // Allows for resources under /dataset/
+            // The request will pass on down the filter/servlet chain.
             return null;
-        }
 
         Operation operation = endpoint.getOperation();
         if ( operation == null ) {
@@ -181,18 +180,6 @@
         // -- Endpoint level authorization
         // Make sure all contribute authentication.
         Auth.allow(user, action.getEndpoint().getAuthPolicy(), ServletOps::errorForbidden);
-        if ( isEmpty(endpointName) && ! endpoint.isUnnamed() ) {
-            // [DISPATCH LEGACY]
-            // If choice was by looking in all named endpoints for a unnamed endpoint
-            // request, ensure all choices allow access.
-            // There may be several endpoints for the operation.
-            // Authorization is the AND of all endpoints.
-            Collection<Endpoint> x = getEndpoints(dataService, operation);
-            if ( x.isEmpty() )
-                throw new InternalErrorException("Inconsistent: no endpoints for "+operation);
-            x.forEach(ept->
-                Auth.allow(user, ept.getAuthPolicy(), ServletOps::errorForbidden));
-        }
         // ---- Authorization checking.
 
         // ---- Handler.
@@ -203,134 +190,72 @@
         return processor;
     }
 
-    /**
-     * Map request to operation name.
-     * Returns the service name (the part after the "/" of the dataset part) or "".
-     */
-    protected static String mapRequestToEndpointName(HttpAction action, DataAccessPoint dataAccessPoint) {
-        return ActionLib.mapRequestToEndpointName(action, dataAccessPoint);
-    }
 
-    // Find the endpoints for an operation.
-    // This is GSP_R/GSP_RW aware.
-    // If asked for GSP_R and there are no endpoints for GSP_R, try GSP_RW.
-    private static Collection<Endpoint> getEndpoints(DataService dataService, Operation operation) {
-        Collection<Endpoint> x = dataService.getEndpoints(operation);
-        if ( x == null || x.isEmpty() ) {
-            if ( operation == GSP_R )
-                x = dataService.getEndpoints(GSP_RW);
-        }
-        return x;
-    }
 
-    /**
-     * Choose an endpoint. This can be with or without endpointName.
-     * If there is no endpoint and the action is on the data service itself (unnamed endpoint)
-     * look for a named endpoint that supplies the operation.
-     */
-    private static Endpoint chooseEndpoint(HttpAction action, DataService dataService, String endpointName) {
-        try {
-            Endpoint ep = chooseEndpointNoLegacy(action, dataService, endpointName);
-            if ( ep != null )
-                return ep;
-            // No dispatch so far.
-
-            if ( ! isEmpty(endpointName) )
-                return ep;
-            // [DISPATCH LEGACY]
-
-            // When it is a unnamed service request (operation on the dataset) and there
-            // is no match, search the named services.
-            Operation operation = chooseOperation(action);
-            // Search for an endpoint that provides the operation.
-            // No guarantee it has the access controls for the operation
-            // but in this case, access control will validate against all possible endpoints.
-            ep = findEndpointForOperation(action, dataService, operation, true);
-            return ep;
-        } catch (ActionErrorException ex) {
-            throw ex;
-        } catch (RuntimeException ex) {
-            // Example: Jetty throws BadMessageException when it is an HTML form and it is too big.
-            ServletOps.errorBadRequest(ex.getMessage());
-            return null;
-        }
-    }
+//    // Find the endpoints for an operation.
+//    // This is GSP_R/GSP_RW aware.
+//    // If asked for GSP_R and there are no endpoints for GSP_R, try GSP_RW.
+//    private static Collection<Endpoint> getEndpoints(DataService dataService, Operation operation) {
+//        Collection<Endpoint> x = dataService.getEndpoints(operation);
+//        if ( x == null || x.isEmpty() ) {
+//            if ( operation == GSP_R ) // [GSP Promote]
+//                x = dataService.getEndpoints(GSP_RW);
+//        }
+//        return x;
+//    }
 
     /**
      * Choose an endpoint.
+     * An endpoint is a name and an operation.
      * <ul>
      * <li>Look by service name to get the EndpointSet</li>
-     * <li>If empty set, return null.</li>
+     * <li>If empty set, respond with error</li>
      * <li>If there is only one choice, return that (may even be the wrong operation
-     *       - processor implmentations must be defensive).</li>
+     *       - processor implementations must be defensive).</li>
      * <li>If multiple choices, classify the operation
      *     (includes custom content-type) and look up by operation.</li>
-     * <li>Return a match wit a r
+     * <li>If not suitable, respond with error
+     * <li>Return an endpoint.
      * </ul>
+     * The endpoint chosen may not be suitable, the operation must do checking.
      */
-    private static Endpoint chooseEndpointNoLegacy(HttpAction action, DataService dataService, String endpointName) {
+    private static Endpoint chooseEndpoint(HttpAction action, DataService dataService, String endpointName) {
         EndpointSet epSet = isEmpty(endpointName) ? dataService.getEndpointSet() : dataService.getEndpointSet(endpointName);
-
-        if ( epSet == null || epSet.isEmpty() )
+        if ( epSet == null || epSet.isEmpty() ) {
             // No matches by name.
-            return null;
+            if ( ! StringUtils.isAnyEmpty(endpointName) )
+                // There was a service name but it was not found.
+                // It may be a URL for static resource.
+                return null;
+            // Dataset URL - "exists" (even if no services) so 404 is wrong.
+            ServletOps.errorBadRequest("No endpoint for request");
+            return null; // Unreachable.
+        }
 
         // If there is one endpoint, dispatch there directly.
         Endpoint ep = epSet.getExactlyOne();
         if ( ep != null )
+            // Single dispatch, may not be valid.
             return ep;
         // No single direct dispatch. Multiple choices (different operation, same endpoint name)
         // Work out which operation we are looking for.
         Operation operation = chooseOperation(action);
         ep = epSet.get(operation);
-        // This also happens in findEndpointForOperation
-        // If a GSP-R request, try for GSP-RW
-        if ( ep == null && Operation.GSP_R.equals(operation) )
-            ep = epSet.get(Operation.GSP_RW);
-        return ep;
-    }
-
-    /**
-     *  Find an endpoint for an operation.
-     *  This searches all endpoints of a {@link DataService} that provide the {@link Operation}.
-     *  This understands that GSP_RW can service GSP_R.
-     *  Used for legacy dispatch.
-     */
-    private static Endpoint findEndpointForOperation(HttpAction action, DataService dataService, Operation operation, boolean preferUnnamed) {
-        Endpoint ep = findEndpointForOperationExact(dataService, operation, preferUnnamed);
-        if ( ep != null )
-            return ep;
-        // Try to find "R" functionality from an RW.
-        if ( GSP_R.equals(operation) )
-            return findEndpointForOperationExact(dataService, GSP_RW, preferUnnamed);
-        // Instead of 404, return 405 if asked for RW but only R available.
-        if ( GSP_RW.equals(operation) && dataService.hasOperation(GSP_R) )
-            ServletOps.errorMethodNotAllowed(action.getMethod());
-        return null;
-    }
-
-    /** Find a matching endpoint for exactly this operation.
-     * If multiple choices, prefer either named or unnamed according
-     * to the flag {@code preferUnnamed}.
-     */
-    private static Endpoint findEndpointForOperationExact(DataService dataService, Operation operation, boolean preferUnnamed) {
-        List<Endpoint> eps = dataService.getEndpoints(operation);
-        if ( eps == null || eps.isEmpty() )
-            return null;
-        // ==== Legacy compatibility.
-        // Find a named service, with preference for named/unnamed.
-        Endpoint epAlt = null;
-        for ( Endpoint ep : eps ) {
-            if ( operation.equals(ep.getOperation()) ) {
-                if ( ep.isUnnamed() && preferUnnamed )
-                    return ep;
-                if ( ! ep.isUnnamed() && ! preferUnnamed )
-                    return ep;
-                epAlt = ep;
+        if ( ep == null ) {
+            if ( GSP_R.equals(operation) )
+                // If asking for GSP_R, and GSP_RW available, pass that back.
+                ep = epSet.get(GSP_RW); // [GSP Promote]
+            else if ( GSP_RW.equals(operation) ) {
+                // If asking for GSP_RW, only GSP_R available -> 405.
+                if ( epSet.contains(GSP_R) )
+                    ServletOps.errorMethodNotAllowed(action.getMethod());
             }
         }
-        // Did not find a preferred one.
-        return epAlt;
+
+        // There are multiple endpoints; if none are suitable, then 400.
+        if ( ep == null )
+            ServletOps.errorBadRequest("No operation for request: "+action.getActionURI());
+        return ep;
     }
 
     /**
@@ -338,16 +263,16 @@
      * It is analysing the HTTP request using global configuration.
      * The decision is based on
      * <ul>
-     * <li>Query parameters (URL query string or HTML form)</li>
-     * <li>Content-Type header</li>
-     * <li>Otherwise it is a plain REST (quads) operation.chooseOperation</li>
+     * <li>HTTP query string parameters (URL query string or HTML form)</li>
+     * <li>Registered Content-Type header</li>
+     * <li>Otherwise it is a plain REST (quads)</li>
      * </ul>
      * The HTTP Method is not considered.
      * <p>
      * The operation is not guaranteed to be supported on every {@link DataService}
      * nor that access control will allow it to be performed.
      */
-    public static Operation chooseOperation(HttpAction action) {
+    private static Operation chooseOperation(HttpAction action) {
         HttpServletRequest request = action.getRequest();
 
         // ---- Dispatch based on HttpParams : Query, Update, GSP.
@@ -401,7 +326,6 @@
                 ServletOps.errorBadRequest(HttpSC.getMessage(HttpSC.BAD_REQUEST_400));
         }
 
-
         // ---- No registered content type, no query parameters.
         // Plain HTTP operation on the dataset handled as quads or rejected.
         return quadsOperation(action, request);
@@ -411,11 +335,8 @@
      * Determine the {@link Operation} for a SPARQL Graph Store Protocol (GSP) action.
      * <p>
      * Assumes, and does not check, that the action is a GSP action.
-     *
-     * @throws ActionErrorException
-     *             (which causes a servlet 4xx response) if the operaton is not permitted.
      */
-    private static Operation gspOperation(HttpAction action, HttpServletRequest request) throws ActionErrorException {
+    private static Operation gspOperation(HttpAction action, HttpServletRequest request) {
         // Check enabled.
         if ( isReadMethod(request) )
             return GSP_R;
@@ -428,11 +349,8 @@
      * whole dataset).
      * <p>
      * Assumes, and does not check, that the action is a Quads action.
-     *
-     * @throws ActionErrorException
-     *             (which causes a servlet 405 response) if the operaton is not permitted.
      */
-    private static Operation quadsOperation(HttpAction action, HttpServletRequest request) throws ActionErrorException {
+    private static Operation quadsOperation(HttpAction action, HttpServletRequest request) {
         // Check enabled. Extends GSP.
         if ( isReadMethod(request) )
             return GSP_R;
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 74671b5..f4e0580 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
@@ -114,6 +114,12 @@
         return counters.value(CounterName.RequestsBad);
     }
 
+    public static boolean sameNameAndOperation(Endpoint ep1, Endpoint ep2) {
+        return 
+            Objects.equals(ep1.getName(), ep2.getName()) && 
+            Objects.equals(ep1.getOperation(), ep2.getOperation()) ;
+    }
+    
     @Override
     public String toString() {
         return getName()+"["+operation+"]";
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/EndpointFactoryRegistry.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/EndpointFactoryRegistry.java
deleted file mode 100644
index 1552b01..0000000
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/EndpointFactoryRegistry.java
+++ /dev/null
@@ -1,26 +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 class EndpointFactoryRegistry {
-
-}
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 4c87213..7ab6ff7 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
@@ -56,6 +56,7 @@
     // Server endpoints.
     public static final Property pServerPing        = property("pingEP");
     public static final Property pServerStats       = property("statsEP");
+    public static final Property pServerMetrics     = property("metricsEP");
 
     // Endpoint description - old style.
     public static final Property pServiceQueryEP                = property("serviceQuery");
@@ -64,10 +65,11 @@
     public static final Property pServiceShaclEP                = property("serviceShacl");
     public static final Property pServiceReadWriteGraphStoreEP  = property("serviceReadWriteGraphStore");
     public static final Property pServiceReadGraphStoreEP       = property("serviceReadGraphStore");
-    public static final Property pServiceReadWriteQuadsEP       = property("serviceReadWriteQuads");
-    public static final Property pServiceReadQuadsEP            = property("serviceReadQuads");
+    // No longer used.
+//    public static final Property pServiceReadWriteQuadsEP       = property("serviceReadWriteQuads");
+//    public static final Property pServiceReadQuadsEP            = property("serviceReadQuads");
 
-    // Operation names : the standard operations. 
+    // Operation names : the standard operations.
     // "alt" names are the same but using "_" not "_".
     public static final Resource opQuery       = resource("query");
     public static final Resource opUpdate      = resource("update");
@@ -79,7 +81,7 @@
     public static final Resource opNoOp        = resource("no-op");
     public static final Resource opNoOp_alt    = resource("no_op");
     public static final Resource opShacl       = resource("shacl");
-    
+
     // Internal
     private static final String stateNameActive     = DataServiceStatus.ACTIVE.name;
     private static final String stateNameOffline    = DataServiceStatus.OFFLINE.name;
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 d3784cf..2cef6c3 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
@@ -21,7 +21,7 @@
 public class ActionErrorException extends RuntimeException {
     private final int rc;
 
-    public ActionErrorException(Throwable ex, String message, int rc) {
+    public ActionErrorException(int rc, String message, Throwable ex) {
         super(message, ex);
         this.rc = 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
index 6ce59dc..27735c9 100644
--- 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
@@ -111,7 +111,7 @@
                 //    global -- cxt.get(ARQ.queryTimeout)
                 //    dataset -- dataset.getContect(ARQ.queryTimeout)
                 //    protocol -- SPARQL_Query.setAnyTimeouts
-                String message = String.format("Query timed out");
+                String message = "Query timed out";
                 ServletOps.responseSendError(response, HttpSC.SERVICE_UNAVAILABLE_503, message);
             } catch (ActionErrorException ex) {
                 if ( ex.getCause() != null )
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
index fa0ea1c..81e7520 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
@@ -430,6 +430,6 @@
 
     @Override
     public String toString() {
-        return request.getRequestURL().toString();
+        return request.getMethod()+" "+request.getRequestURL().toString();
     }
 }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQLQueryProcessor.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQLQueryProcessor.java
index 2efc50d..282892c 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQLQueryProcessor.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQLQueryProcessor.java
@@ -253,11 +253,11 @@
         } catch (ActionErrorException ex) {
             throw ex;
         } catch (QueryParseException ex) {
-            ServletOps.errorBadRequest("Parse error: \n" + queryString + "\n\r" + SPARQLProtocol.messageForException(ex));
+            ServletOps.errorBadRequest("Parse error: \n" + queryString + "\n" + SPARQLProtocol.messageForException(ex));
         }
         // Should not happen.
         catch (QueryException ex) {
-            ServletOps.errorBadRequest("Error: \n" + queryString + "\n\r" + ex.getMessage());
+            ServletOps.errorBadRequest("Error: \n" + queryString + "\n" + ex.getMessage());
         }
 
         // Assumes finished whole thing by end of sendResult.
@@ -277,7 +277,7 @@
         }
         catch (QueryParseException ex) {
             // Late stage static error (e.g. bad fixed Lucene query string).
-            ServletOps.errorBadRequest("Query parse error: \n" + queryString + "\n\r" + SPARQLProtocol.messageForException(ex));
+            ServletOps.errorBadRequest("Query parse error: \n" + queryString + "\n" + SPARQLProtocol.messageForException(ex));
         }
         catch (QueryCancelledException ex) {
             // Additional counter information.
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
index 6bee2ee..21f6563 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
@@ -37,22 +37,50 @@
 
 public class ServletOps {
 
+    /** Send an HTTP error response. 
+     *  Include an optional message in the body (as text/plain), if provided.
+     *  Note that we do not set a custom Reason Phrase.
+     *  <br/>
+     *  HTTPS/2 does not have a "Reason Phrase". 
+     * 
+     * @param response
+     * @param statusCode
+     * @param message
+     */
     public static void responseSendError(HttpServletResponse response, int statusCode, String message) {
+        response.setStatus(statusCode);
+        if ( message != null )
+            writeMessagePlainText(response, message);
+        //response.sendError(statusCode, message);
+    }
+
+    /** Send an HTTP response with no body */
+    public static void responseSendError(HttpServletResponse response, int statusCode) {
+        response.setStatus(statusCode);
+    }
+
+    /** Write a plain text body.
+     * <p>
+     * Use Content-Length so the connection is preserved.
+     */
+    public static void writeMessagePlainText(HttpServletResponse response, String message) {
+        if ( message == null )
+            return;
         try {
-            response.sendError(statusCode, message);
+            if ( ! message.endsWith("\n") )
+                message = message+"\n";
+            response.setContentLength(message.length());
+            response.setContentType(WebContent.contentTypeTextPlain);
+            response.setCharacterEncoding(WebContent.charsetUTF8);
+            ServletOps.setNoCache(response);
+            try(ServletOutputStream out = response.getOutputStream()){
+                out.print(message);
+            }
         } catch (IOException ex) {
             errorOccurred(ex);
         } catch (IllegalStateException ex) {}
     }
-
-    public static void responseSendError(HttpServletResponse response, int statusCode) {
-        try {
-            response.sendError(statusCode);
-        } catch (IOException ex) {
-            errorOccurred(ex);
-        }
-    }
-
+    
     public static void successNoContent(HttpAction action) {
         success(action, HttpSC.NO_CONTENT_204);
     }
@@ -78,6 +106,7 @@
     public static void successPage(HttpAction action, String message) {
         try {
             action.response.setContentType("text/html");
+            action.response.setCharacterEncoding(WebContent.charsetUTF8);
             action.response.setStatus(HttpSC.OK_200);
             PrintWriter out = action.response.getWriter();
             out.println("<html>");
@@ -146,11 +175,11 @@
     }
 
     public static void error(int statusCode) {
-        throw new ActionErrorException(null, null, statusCode);
+        throw new ActionErrorException(statusCode, null, null);
     }
 
     public static void error(int statusCode, String string) {
-        throw new ActionErrorException(null, string, statusCode);
+        throw new ActionErrorException(statusCode, string, null);
     }
 
     public static void errorOccurred(String message) {
@@ -162,7 +191,7 @@
     }
 
     public static void errorOccurred(String message, Throwable ex) {
-        throw new ActionErrorException(ex, message, HttpSC.INTERNAL_SERVER_ERROR_500);
+        throw new ActionErrorException(HttpSC.INTERNAL_SERVER_ERROR_500, message, ex);
     }
 
     public static String formatForLog(String string) {
diff --git a/jena-fuseki2/jena-fuseki-docker/Dockerfile b/jena-fuseki2/jena-fuseki-docker/Dockerfile
new file mode 100644
index 0000000..abe6d9c
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-docker/Dockerfile
@@ -0,0 +1,115 @@
+## 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.
+
+## Apache Jena Fuseki server Dockerfile.
+
+## This Dockefile builds a reduced footprint container.
+
+ARG OPENJDK_VERSION=14
+ARG ALPINE_VERSION=3.12.0
+ARG JENA_VERSION=""
+
+# Internal, passed between stages.
+ARG FUSEKI_DIR=/fuseki
+ARG FUSEKI_JAR=jena-fuseki-server-${JENA_VERSION}.jar
+ARG JAVA_MINIMAL=/opt/java-minimal
+
+## ---- Stage: Download and build java.
+FROM openjdk:${OPENJDK_VERSION}-alpine AS base
+
+ARG JAVA_MINIMAL
+ARG JENA_VERSION
+ARG FUSEKI_DIR
+ARG FUSEKI_JAR
+ARG REPO=https://repo1.maven.org/maven2
+ARG JAR_URL=${REPO}/org/apache/jena/jena-fuseki-server/${JENA_VERSION}/${FUSEKI_JAR}
+
+RUN [ "${JENA_VERSION}" != "" ] || { echo -e '\n**** Set JENA_VERSION ****\n' ; exit 1 ; }
+RUN echo && echo "==== Docker build for Apache Jena Fuseki ${JENA_VERSION} ====" && echo
+
+# Alpine: For objcopy used in jlink
+RUN apk add --no-cache curl binutils
+
+## -- Fuseki installed and runs in /fuseki.
+WORKDIR $FUSEKI_DIR
+
+## -- Download the jar file.
+COPY download.sh .
+RUN chmod a+x download.sh
+
+# Download, with check of the SHA1 checksum.
+RUN ./download.sh --chksum sha1 "$JAR_URL"
+
+## -- Alternatives to download : copy already downloaded.
+## COPY ${FUSEKI_JAR} .
+
+## Use Docker ADD - does not retry, does not check checksum, and may run every build.
+## ADD "$JAR_URL"
+
+## -- Make reduced Java JDK
+RUN \
+  JDEPS="$(jdeps --multi-release base --print-module-deps --ignore-missing-deps ${FUSEKI_JAR})" && \
+  jlink \
+        --compress 2 --strip-debug --no-header-files --no-man-pages \
+        --output "${JAVA_MINIMAL}" \
+        --add-modules "${JDEPS}"
+
+ADD entrypoint.sh .
+ADD log4j2.properties .
+
+# Run as this user
+# -H : no home directorry
+# -D : no password
+
+RUN adduser -H -D fuseki fuseki
+
+## ---- Stage: Build runtime
+FROM alpine:${ALPINE_VERSION}
+
+## Import ARGs
+ARG JENA_VERSION
+ARG JAVA_MINIMAL
+ARG FUSEKI_DIR
+ARG FUSEKI_JAR
+
+COPY --from=base /opt/java-minimal /opt/java-minimal
+COPY --from=base /fuseki /fuseki
+COPY --from=base /etc/passwd /etc/passwd
+
+WORKDIR $FUSEKI_DIR
+
+ARG LOGS=${FUSEKI_DIR}/logs
+ARG DATA=${FUSEKI_DIR}/databases
+
+RUN \
+    mkdir -p $LOGS && \
+    mkdir -p $DATA && \
+    chown -R fuseki ${FUSEKI_DIR} && \
+    chmod a+x entrypoint.sh 
+
+## Default environment variables.
+ENV \
+    JAVA_HOME=${JAVA_MINIMAL}           \
+    JAVA_OPTIONS="-Xmx2048m -Xms2048m"  \
+    JENA_VERSION=${JENA_VERSION}        \
+    FUSEKI_JAR="${FUSEKI_JAR}"          \
+    FUSEKI_DIR="${FUSEKI_DIR}"
+
+USER fuseki
+
+EXPOSE 3030
+
+ENTRYPOINT ["./entrypoint.sh" ]
+CMD []
diff --git a/jena-fuseki2/jena-fuseki-docker/README.md b/jena-fuseki2/jena-fuseki-docker/README.md
new file mode 100644
index 0000000..eecdcb8
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-docker/README.md
@@ -0,0 +1,109 @@
+# Apache Jena Fuseki Docker Tools
+
+This package contains a Dockerfile, docker-compose file, and helper scripts to
+create a docker container for Apache Jena Fuseki.
+
+The docker container is based on 
+[Fuseki main](https://jena.apache.org/documentation/fuseki2/fuseki-main)
+for running a SPARQL server.
+
+There is no UI - all configuration is by command line and all usage by via the
+network protocols.
+
+Databases can be mounted outside the docker container so they are preserved when
+the container terminates.
+
+This build system allows the user to customize the docker image.
+
+The docker build downloads the server binary from 
+[Maven central](https://repo1.maven.org/maven2/org/apache/jena/jena-fuseki-server/),
+checking the download against the SHA1 checksum.
+
+## Database
+
+There is a volume mapping "./databases" in the current directory into the server.
+This can be used to contain databases outside, but accessible to, the container
+that do not get deleted when the container exits.
+
+See examples below.
+
+## Build
+
+Choose the version number of Apache Jena release you wish to use. This toolkit
+defaults to the version of the overall Jena release it was part of. It is best
+to use the release of this set of tools from the same release of the desired
+server.
+
+    docker-compose build --build-arg JENA_VERSION=3.16.0
+
+Note the build command must provide the version number.
+
+## Test Run
+
+`docker-compose run` cam be used to test the build from the previous section.
+
+Examples:
+
+Start Fuseki with an in-memory, updatable dataset at http://<i>host</i>:3030/ds
+
+    docker-compose run --rm --service-ports fuseki --mem /ds
+
+Load a TDB2 database, and expose, read-only, via docker:
+
+    mkdir -p databases/DB2
+    tdb2.tdbloader --loc databases/DB2 MyData.ttl
+    # Publish read-only
+    docker-compose run --rm --name MyServer --service-ports fuseki --tdb2 --loc databases/DB2 /ds
+
+To allow update on the database, add `--update`. Updates are persisted.
+
+    docker-compose run --rm --name MyServer --service-ports fuseki --tdb2 --update --loc databases/DB2 /ds
+
+See
+[fuseki-configuration](https://jena.apache.org/documentation/fuseki2/fuseki-configuration.html)
+for more information on command line arguments.
+
+To use `docker-compose up`, edit the `docker-compose.yaml` to set the Fuseki
+command line arguments appropriately.
+
+## Layout
+
+The default layout in the container is:
+
+| Path  | Use | 
+| ----- | --- |
+| /opt/java-minimal | A reduced size Java runtime                      |
+| /fuseki | The Fuseki installation                                    |
+| /fuseki/log4j2.properties | Logging configuration                    |
+| /fuseki/databases/ | Directory for a volume for persistent databases |
+
+## Setting JVM arguments
+
+Use `JAVA_OPTIONS`:
+
+    docker-compose run --service-ports --rm -e JAVA_OPTIONS="-Xmx1048m -Xms1048m" --name MyServer fuseki --mem /ds
+
+## Docker Commands
+
+If you prefer to use `docker` directly:
+
+Build:
+
+    docker build --force-rm --build-arg JENA_VERSION=3.16.0 -t fuseki .
+
+Run:
+
+    docker run -i --rm -p "3030:3030" --name MyServer -t fuseki --mem /ds
+
+With databases on a bind mount to host filesystem directory:
+
+    MNT="--mount type=bind,src=$PWD/databases,dst=/fuseki/databases"
+    docker run -i --rm -p "3030:3030" $MNT --name MyServer -t fuseki --tdb2 --update --loc databases/DB2 /ds
+
+## Version specific notes:
+
+* Versions of Jena up to 3.14.0 use Log4j1 for logging. The docker will build will ignore
+   the log4j2.properties file
+* Version 3.15.0: When run, a warning will be emitted.  
+  `WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.`  
+  This can be ignored.
diff --git a/jena-fuseki2/jena-fuseki-docker/assembly-docker.xml b/jena-fuseki2/jena-fuseki-docker/assembly-docker.xml
new file mode 100644
index 0000000..b62113f
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-docker/assembly-docker.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+
+<!-- 
+  The docker tools distribution.
+-->
+
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+  
+  <id>distribution</id>
+
+  <formats>
+    <format>zip</format>
+  </formats>
+
+  <baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
+
+  <!-- If including the jar
+  <dependencySets>
+    <dependencySet>
+      <useProjectArtifact>false</useProjectArtifact>
+      <includes>
+        <include>org.apache.jena:jena-fuseki-server:jar</include>
+      </includes>
+      <outputFileNameMapping>fuseki-server.jar</outputFileNameMapping>
+      <outputFileNameMapping>jena-fuseki-server-${artifact.version}.jar</outputFileNameMapping>
+    </dependencySet>
+  </dependencySets>
+  -->
+
+  <files>
+    <file>
+      <source>dist/LICENSE</source>
+      <destName>LICENSE</destName>
+    </file>
+    <file>
+      <source>dist/NOTICE</source>
+      <destName>NOTICE</destName>
+    </file>
+  </files>
+
+  <fileSets>
+    <fileSet>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>Dockerfile*</include>
+        <include>docker-compose.yaml</include>
+        <include>README*</include>
+        <include>log4j2.properties</include>
+        <include>entrypoint.sh</include>
+        <include>download.sh</include>
+        <include>.dockerignore</include>
+      </includes>
+    </fileSet>
+
+  </fileSets>
+</assembly>
diff --git a/jena-fuseki2/jena-fuseki-docker/dist/LICENSE b/jena-fuseki2/jena-fuseki-docker/dist/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-docker/dist/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/jena-fuseki2/jena-fuseki-docker/dist/NOTICE b/jena-fuseki2/jena-fuseki-docker/dist/NOTICE
new file mode 100644
index 0000000..5a33516
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-docker/dist/NOTICE
@@ -0,0 +1,5 @@
+Apache Jena - module Fuseki (Docker tools)
+Copyright - The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/jena-fuseki2/jena-fuseki-docker/docker-compose.yaml b/jena-fuseki2/jena-fuseki-docker/docker-compose.yaml
new file mode 100644
index 0000000..4433e9d
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-docker/docker-compose.yaml
@@ -0,0 +1,32 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.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.
+
+version: '3.0'
+services:
+  fuseki:
+    ## Example:
+    ## command: [ "--tdb2", "--update", "--loc", "databases/DB2", "/ds" ]
+    build:
+      context: .
+      dockerfile: Dockerfile
+    image: fuseki
+    ports:
+      - "3030:3030"
+    volumes:
+      - ./logs:/fuseki/logs
+      - ./databases:/fuseki/databases
+#volumes:
diff --git a/jena-fuseki2/jena-fuseki-docker/download.sh b/jena-fuseki2/jena-fuseki-docker/download.sh
new file mode 100755
index 0000000..bf4e2ae
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-docker/download.sh
@@ -0,0 +1,147 @@
+#!/bin/sh
+
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.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.
+
+# This is an ash/dash script (it uses "local"), not a bash script.
+# It can run in an Alpine image durign a docker build.
+#
+# The advantage over using docker ADD is that it checks
+# whether download file is already present and does not
+# download each time.
+#
+# Shell script to download URL and check the checksum
+
+USAGE="Usage: $(basename "$0") --chksum [sha1|sha512] URL"
+
+if [ $# -eq 0 ]
+then
+    echo "$USAGE" 2>&1
+    exit 1
+fi
+
+CHKSUM_TYPE='unset'
+
+while [ $# -gt 0 ] ; do
+    case "$1" in
+	--chksum|-chksum|-sha|--sha)
+	    if [ $# -lt 2 ]
+	    then
+		echo "$USAGE" 1>&2
+		exit 1
+	    fi
+	    CHKSUM_TYPE=$2
+	    shift
+	    shift
+	    ;;
+	-h|--help)
+	    echo "$USAGE" 1>&2
+	    exit 0
+	    ;;
+	-*)
+	    echo "$USAGE" 1>&2
+	    exit 1
+	    ;;
+	*)
+	    if [ $# -ne 1 ]
+	    then
+		echo "$USAGE" 1>&2
+		exit 1
+	    fi
+	    URL="$1"
+	    shift
+	    ;;
+    esac
+done
+
+case "${CHKSUM_TYPE}" in
+    unset)
+	echo "$USAGE" 1>&2
+	exit 1
+	;;
+    sha*|md5) ;;
+    *)
+	echo "Bad checksum type: '$CHKSUM_TYPE' (must start 'sha' or be 'md5')" 2>&1
+	exit 1	 
+	;;
+esac
+
+## ---- Script starts ----
+
+ARTIFACT_URL="${URL}"
+ARTIFACT_NAME="$(basename "$ARTIFACT_URL")"
+
+# -------- Checksum details
+
+CHKSUM_EXT=".${CHKSUM_TYPE}"
+CHKSUM_URL="${ARTIFACT_URL}${CHKSUM_EXT}"
+CHKSUM_FILE="${ARTIFACT_NAME}${CHKSUM_EXT}"
+CHKSUMPROG="${CHKSUM_TYPE}sum"
+# --------
+
+CURL_FETCH_OPTS="-s -S --fail --location --max-redirs 3"
+if false
+then
+    echo "ARTIFACT_URL=$ARTIFACT_URL"
+    echo "CHKSUM_URL=$CHKSUM_URL"
+fi
+
+download() { # URL
+    local URL="$1"
+    local FN="$(basename "$URL")"
+    if [ ! -e "$FN" ]
+    then
+	echo "Fetching $URL"
+	curl $CURL_FETCH_OPTS "$URL" --output "$FN" \
+	    || { echo "Bad download of $FN" 2>&1 ; return 1 ; }
+    else
+	echo "$FN already present"
+    fi
+    return 0
+}
+
+checkChksum() { # Filename checksum
+    local FN="$1"
+    local CHKSUM="$2"
+    if [ ! -e "$FN" ]
+    then
+	echo "No such file: '$FN'" 2>&1
+	exit 1
+    fi
+    # NB Two spaces required for busybox
+    echo "$CHKSUM  $FN" | ${CHKSUMPROG} -c > /dev/null
+}
+
+download "$ARTIFACT_URL" || exit 1
+
+if [ -z "$CHKSUM" ]
+then
+    # Checksum not previously set.
+    # Extract from file, copes with variations in content (filename or not)
+    download "$CHKSUM_URL" || exit 1
+    CHKSUM="$(cut -d' ' -f1 "$CHKSUM_FILE")"
+fi
+
+checkChksum "${ARTIFACT_NAME}" "$CHKSUM"
+if [ $? = 0 ]
+then
+    echo "Good download: $ARTIFACT_NAME"
+else
+    echo "BAD download !!!! $ARTIFACT_NAME"
+    echo "To retry: delete downloaded files and try again"
+    exit 1
+fi
diff --git a/jena-fuseki2/jena-fuseki-docker/entrypoint.sh b/jena-fuseki2/jena-fuseki-docker/entrypoint.sh
new file mode 100755
index 0000000..a4ab8cd
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-docker/entrypoint.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+## env | sort
+exec "$JAVA_HOME/bin/java" $JAVA_OPTIONS -jar "${FUSEKI_DIR}/${FUSEKI_JAR}" "$@"
diff --git a/jena-fuseki2/jena-fuseki-docker/log4j2.properties b/jena-fuseki2/jena-fuseki-docker/log4j2.properties
new file mode 100644
index 0000000..9235085
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-docker/log4j2.properties
@@ -0,0 +1,70 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+status = error
+name = PropertiesConfig
+filters = threshold
+
+filter.threshold.type = ThresholdFilter
+filter.threshold.level = INFO
+
+appender.console.type = Console
+appender.console.name = OUT
+appender.console.target = SYSTEM_OUT
+appender.console.layout.type = PatternLayout
+## appender.console.layout.pattern = %d{HH:mm:ss} %-5p %-15c{1} :: %m%n
+## Include date.
+appender.console.layout.pattern = [%d{yyyy-MM-dd HH:mm:ss}] %-5p %-15c{1} :: %m%n
+
+## To a file.
+## appender.file.type = File
+## appender.file.name = FILE
+## appender.file.fileName=/fuseki/logs/log.fuseki
+## appender.file.layout.type=PatternLayout
+## appender.file.layout.pattern = [%d{yyyy-MM-dd HH:mm:ss}] %-5p %-15c{1} :: %m%n
+
+rootLogger.level                  = INFO
+rootLogger.appenderRef.stdout.ref = OUT
+
+logger.jena.name  = org.apache.jena
+logger.jena.level = INFO
+
+logger.arq-exec.name  = org.apache.jena.arq.exec
+logger.arq-exec.level = INFO
+
+logger.riot.name  = org.apache.jena.riot
+logger.riot.level = INFO
+
+logger.fuseki.name  = org.apache.jena.fuseki
+logger.fuseki.level = INFO
+
+logger.fuseki-fuseki.name  = org.apache.jena.fuseki.Fuseki
+logger.fuseki-fuseki.level = INFO
+
+logger.fuseki-server.name  = org.apache.jena.fuseki.Server
+logger.fuseki-server.level = INFO
+
+logger.fuseki-admin.name  = org.apache.jena.fuseki.Admin
+logger.fuseki-admin.level = INFO
+
+logger.jetty.name  = org.eclipse.jetty
+logger.jetty.level = WARN
+
+# May be useful to turn up to DEBUG if debugging HTTP communication issues
+logger.apache-http.name   = org.apache.http
+logger.apache-http.level  = WARN
+
+logger.shiro.name = org.apache.shiro
+logger.shiro.level = WARN
+# Hide bug in Shiro 1.5.x
+logger.shiro-realm.name = org.apache.shiro.realm.text.IniRealm
+logger.shiro-realm.level = ERROR
+
+# This goes out in NCSA format
+appender.plain.type = Console
+appender.plain.name = PLAIN
+appender.plain.layout.type = PatternLayout
+appender.plain.layout.pattern = %m%n
+
+logger.request-log.name                   = org.apache.jena.fuseki.Request
+logger.request-log.additivity             = false
+logger.request-log.level                  = OFF
+logger.request-log.appenderRef.plain.ref  = PLAIN
diff --git a/jena-fuseki2/jena-fuseki-docker/pom.xml b/jena-fuseki2/jena-fuseki-docker/pom.xml
new file mode 100644
index 0000000..b400e0e
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-docker/pom.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" 
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <name>Apache Jena - Fuseki Docker Tools</name>
+  <artifactId>jena-fuseki-docker</artifactId>
+  <version>3.17.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <parent>
+    <groupId>org.apache.jena</groupId>
+    <artifactId>jena-fuseki</artifactId>
+    <version>3.17.0-SNAPSHOT</version>
+    <relativePath>..</relativePath>
+  </parent> 
+  
+  <description>Fuseki Docker</description>
+
+  <build>
+    <plugins>
+
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>create-zip-assembly</id>
+            <phase>package</phase>
+            <!--<phase />-->
+            <goals><goal>single</goal></goals>
+            <configuration>
+              <appendAssemblyId>false</appendAssemblyId>
+              <tarLongFileMode>posix</tarLongFileMode>
+              <descriptors>
+                <descriptor>assembly-docker.xml</descriptor>
+              </descriptors>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      
+    </plugins>
+
+  </build>
+
+</project>
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
index 2b09b94..65438ca 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
@@ -27,6 +27,8 @@
 import javax.servlet.Filter;
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 import org.apache.jena.atlas.lib.Pair;
 import org.apache.jena.atlas.logging.FmtLog;
@@ -38,9 +40,10 @@
 import org.apache.jena.fuseki.auth.Auth;
 import org.apache.jena.fuseki.auth.AuthPolicy;
 import org.apache.jena.fuseki.build.FusekiConfig;
+import org.apache.jena.fuseki.ctl.ActionMetrics;
 import org.apache.jena.fuseki.ctl.ActionPing;
 import org.apache.jena.fuseki.ctl.ActionStats;
-import org.apache.jena.fuseki.jetty.FusekiErrorHandler1;
+import org.apache.jena.fuseki.jetty.FusekiErrorHandler;
 import org.apache.jena.fuseki.jetty.JettyHttps;
 import org.apache.jena.fuseki.jetty.JettyLib;
 import org.apache.jena.fuseki.metrics.MetricsProviderRegistry;
@@ -242,8 +245,10 @@
         private int                      serverHttpsPort    = -1;
         private boolean                  networkLoopback    = false;
         private boolean                  verbose            = false;
-        private boolean                  withStats          = false;
         private boolean                  withPing           = false;
+        private boolean                  withMetrics        = false;
+        private boolean                  withStats          = false;
+
         private Map<String, String>      corsInitParams     = null;
 
         // Server wide authorization policy.
@@ -403,6 +408,14 @@
             return this;
         }
 
+        /** Add the "/$/ping" servlet that responds to HTTP very efficiently.
+         * This is useful for testing whether a server is alive, for example, from a load balancer.
+         */
+        public Builder enablePing(boolean withPing) {
+            this.withPing = withPing;
+            return this;
+        }
+
         /** Add the "/$/stats" servlet that responds with stats about the server,
          * including counts of all calls made.
          */
@@ -411,11 +424,9 @@
             return this;
         }
 
-        /** Add the "/$/ping" servlet that responds to HTTP very efficiently.
-         * This is useful for testing whether a server is alive, for example, from a load balancer.
-         */
-        public Builder enablePing(boolean withPing) {
-            this.withPing = withPing;
+        /** Add the "/$/metrics" servlet that responds with Prometheus metrics about the server. */
+        public Builder enableMetrics(boolean withMetrics) {
+            this.withMetrics = withMetrics;
             return this;
         }
 
@@ -531,6 +542,7 @@
 
             withPing  = argBoolean(server, FusekiVocab.pServerPing,  false);
             withStats = argBoolean(server, FusekiVocab.pServerStats, false);
+            withMetrics = argBoolean(server, FusekiVocab.pServerMetrics, false);
 
             // Extract settings - the server building is done in buildSecurityHandler,
             // buildAccessControl.  Dataset and graph level happen in assemblers.
@@ -972,7 +984,7 @@
                 contextPath = "/" + contextPath;
             ServletContextHandler context = new ServletContextHandler();
             context.setDisplayName(Fuseki.servletRequestLogName);
-            context.setErrorHandler(new FusekiErrorHandler1());
+            context.setErrorHandler(new FusekiErrorHandler());
             context.setContextPath(contextPath);
             // SPARQL Update by HTML - not the best way but.
             context.setMaxFormContentSize(1024*1024);
@@ -1003,10 +1015,12 @@
             addFilter(context, "/*", ff);
 
             // and then any additional servlets and filters.
-            if ( withStats )
-                addServlet(context, "/$/stats/*", new ActionStats());
             if ( withPing )
                 addServlet(context, "/$/ping", new ActionPing());
+            if ( withStats )
+                addServlet(context, "/$/stats/*", new ActionStats());
+            if ( withMetrics )
+                addServlet(context, "/$/metrics", new ActionMetrics());
 
             servlets.forEach(p-> addServlet(context, p.getLeft(), p.getRight()));
             filters.forEach (p-> addFilter(context, p.getLeft(), p.getRight()));
@@ -1017,6 +1031,33 @@
                 ServletHolder staticContent = new ServletHolder(staticServlet);
                 staticContent.setInitParameter("resourceBase", staticContentDir);
                 context.addServlet(staticContent, "/");
+            } else {
+                // Backstop servlet
+                // Jetty default is 404 on GET and 405 otherwise
+                HttpServlet staticServlet = new Servlet404();
+                ServletHolder staticContent = new ServletHolder(staticServlet);
+                context.addServlet(staticContent, "/");
+            }
+        }
+
+        /** 404 for HEAD/GET/POST/PUT */
+        static class Servlet404 extends HttpServlet {
+            // service()?
+            @Override
+            protected void doHead(HttpServletRequest req, HttpServletResponse resp)     { err404(req, resp); }
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp)      { err404(req, resp); }
+            @Override
+            protected void doPost(HttpServletRequest req, HttpServletResponse resp)     { err404(req, resp); }
+            @Override
+            protected void doPut(HttpServletRequest req, HttpServletResponse resp)      { err404(req, resp); }
+            //protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
+            //protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
+            //protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
+            private static void err404(HttpServletRequest req, HttpServletResponse response) {
+                try {
+                    response.sendError(404, "NOT FOUND");
+                } catch (IOException ex) {}
             }
         }
 
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
index d1aeb1e..5e4beae 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
@@ -86,6 +86,7 @@
     private static ArgDecl  argCORS         = new ArgDecl(ArgDecl.NoValue, "withCORS", "cors", "CORS");
     private static ArgDecl  argWithPing     = new ArgDecl(ArgDecl.NoValue, "withPing", "ping");
     private static ArgDecl  argWithStats    = new ArgDecl(ArgDecl.NoValue, "withStats", "stats");
+    private static ArgDecl  argWithMetrics  = new ArgDecl(ArgDecl.NoValue,  "withMetrics", "metrics");
 
     private static ArgDecl  argAuth         = new ArgDecl(ArgDecl.HasValue, "auth");
 
@@ -170,6 +171,7 @@
 //            add(argRealm, "--realm=REALM", "Realm name");
         add(argWithPing,    "--ping",   "Enable /$/ping");
         add(argWithStats,   "--stats",  "Enable /$/stats");
+        add(argWithMetrics, "--metrics",  "Enable /$/metrics");
 
         super.modVersion.addClass(Fuseki.class);
     }
@@ -397,6 +399,7 @@
         serverConfig.withCORS = contains(argCORS);
         serverConfig.withPing = contains(argWithPing);
         serverConfig.withStats = contains(argWithStats);
+        serverConfig.withMetrics = contains(argWithMetrics);
 
 //            if ( contains(argGZip) ) {
 //                if ( !hasValueOfTrue(argGZip) && !hasValueOfFalse(argGZip) )
@@ -507,6 +510,9 @@
         if ( serverConfig.withStats )
             builder.enableStats(true);
 
+        if ( serverConfig.withMetrics )
+            builder.enableMetrics(true);
+
         return builder.build();
     }
 
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
index 73f8da0..8e83e61 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
@@ -40,6 +40,7 @@
     public boolean withCORS           = false;
     public boolean withPing           = false;
     public boolean withStats          = false;
+    public boolean withMetrics        = false;
 
     // This is set ...
     public DatasetGraph dsg           = null;
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestLib.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestLib.java
index 89a9d1c..a0f47eb 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestLib.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestLib.java
@@ -30,7 +30,7 @@
     public static void expect401(Runnable runnable) {
         expectFail(runnable, HttpSC.Code.UNAUTHORIZED);
     }
-        
+
     public static void expect403(Runnable runnable) {
         expectFail(runnable, HttpSC.Code.FORBIDDEN);
     }
@@ -38,13 +38,12 @@
     public static void expect404(Runnable runnable) {
         expectFail(runnable, HttpSC.Code.NOT_FOUND);
     }
-    
+
     public static void expectFail(Runnable runnable, Code code) {
         if ( code == null || ( 200 <= code.getCode() && code.getCode() < 300 ) ) {
             runnable.run();
             return;
         }
-
         try {
           runnable.run();
           fail("Failed: Got no exception: Expected HttpException "+code.getCode());
@@ -55,12 +54,23 @@
       }
     }
 
+    public static int expectFail(Runnable runnable) {
+        try {
+          runnable.run();
+          fail("Failed: Got no exception: Expected HttpException");
+          return -1;
+      } catch (HttpException ex) {
+          return ex.getStatusCode();
+      }
+    }
+
+
     // Same - except a QueryExceptionHTTP.
-    
+
     public static void expectQuery401(Runnable runnable) {
         expectQueryFail(HttpSC.Code.UNAUTHORIZED, runnable);
     }
-        
+
     public static void expectQuery403(Runnable runnable) {
         expectQueryFail(HttpSC.Code.FORBIDDEN, runnable);
     }
@@ -68,13 +78,13 @@
     public static void expectQuery404(Runnable runnable) {
         expectQueryFail(HttpSC.Code.NOT_FOUND, runnable);
     }
-    
+
     public static void expectQueryFail(Code code, Runnable runnable) {
         if ( code == null || ( 200 <= code.getCode() && code.getCode() < 300 ) ) {
             runnable.run();
             return;
         }
-        
+
         try {
           runnable.run();
           fail("Failed: Got no exception: Expected QueryExceptionHTTP "+code.getCode());
@@ -84,7 +94,7 @@
           throw ex;
       }
     }
-    
+
     public static void expectOK(Runnable runnable) {
         runnable.run();
     }
@@ -92,7 +102,7 @@
     public static void expectQueryOK(Runnable runnable) {
         runnable.run();
     }
-    
+
     public static void expectQueryAccessFail(Runnable runnable) {
         try {
              runnable.run();
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
index 408fa1b..6bdf2de 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
@@ -31,6 +31,8 @@
   , TestFusekiCustomOperation.class
   , TestFusekiMainCmd.class
   , TestStdSetup.class
+  , TestConfigFile.class
+  , TestHTTP.class
   , TestFusekiShaclValidation.class
 })
 public class TS_FusekiMain {}
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java
index b271d60..4721f51 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java
@@ -22,6 +22,7 @@
 import static org.apache.jena.fuseki.test.FusekiTest.expect404;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import org.apache.jena.atlas.lib.StrUtils;
@@ -32,22 +33,23 @@
 import org.apache.jena.rdfconnection.RDFConnectionFactory;
 import org.apache.jena.rdfconnection.RDFConnectionRemote;
 import org.apache.jena.rdfconnection.RDFConnectionRemoteBuilder;
+import org.apache.jena.riot.web.HttpOp;
 import org.apache.jena.sparql.core.Var;
 import org.junit.Test;
 
-/** Test server configuration by configuration file */ 
+/** Test server configuration by configuration file */
 public class TestConfigFile {
 
     private static final String DIR = "testing/Config/";
-    
+
     private static final String PREFIXES = StrUtils.strjoinNL
-        ("PREFIX afn: <http://jena.apache.org/ARQ/function#>" 
+        ("PREFIX afn: <http://jena.apache.org/ARQ/function#>"
         ,"PREFIX fuseki: <http://jena.apache.org/fuseki#>"
         ,"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"
         ,"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>"
         , ""
         );
-    
+
     private static RDFConnection namedServices(String baseURL) {
         return RDFConnectionRemote.create()
             .destination(baseURL)
@@ -56,9 +58,9 @@
             .gspEndpoint("data")
             .build();
     }
-    
+
     @Test public void basic () {
-        int port = WebLib.choosePort(); 
+        int port = WebLib.choosePort();
         FusekiServer server = server(port, "basic.ttl");
         server.start();
         try ( RDFConnection conn = RDFConnectionFactory.connect("http://localhost:"+port+"/ds") ) {
@@ -70,7 +72,7 @@
     }
 
     @Test public void context() {
-        int port = WebLib.choosePort(); 
+        int port = WebLib.choosePort();
         FusekiServer server = server(port, "context.ttl");
         server.start();
         try {
@@ -96,7 +98,7 @@
     }
 
     @Test public void stdServicesNamed () {
-        int port = WebLib.choosePort(); 
+        int port = WebLib.choosePort();
         FusekiServer server = server(port, "std-named.ttl");
         String serverURL = "http://localhost:"+port+"/ds-named";
         server.start();
@@ -108,7 +110,7 @@
                 Graph g = conn.fetch().getGraph();
                 assertEquals(1, g.size());
             }
-            
+
             // These should not work because there is a blocking dataset service (no-op).
             try ( RDFConnection conn =  RDFConnectionFactory.connect(serverURL) ) {
                 expect400(()->conn.update("INSERT DATA { <x:s> <x:p> 123 }"));
@@ -120,37 +122,37 @@
             server.stop();
         }
     }
-    
-    @Test public void stdServicesDirect () {
-        int port = WebLib.choosePort(); 
+
+    @Test public void stdServicesDirect() {
+        int port = WebLib.choosePort();
         FusekiServer server = server(port, "std-dataset.ttl");
         String serverURL = "http://localhost:"+port+"/ds-direct";
         server.start();
         try {
-            try ( RDFConnection conn =  RDFConnectionFactory.connect(serverURL) ) {
-                conn.update("INSERT DATA { <x:s> <x:p> 123 }");
-                conn.queryAsk("ASK{}");
-                Graph g = conn.fetch().getGraph();
-                assertEquals(1, g.size());
-            }
-            
+//            try ( RDFConnection conn =  RDFConnectionFactory.connect(serverURL) ) {
+//                conn.update("INSERT DATA { <x:s> <x:p> 123 }");
+//                conn.queryAsk("ASK{}");
+//                Graph g = conn.fetch().getGraph();
+//                assertEquals(1, g.size());
+//            }
+
             // No named endpoints.
             try ( RDFConnection conn = namedServices(serverURL) ) {
                 expect404(()->conn.update("INSERT DATA { <x:s> <x:p> 123 }"));
                 expect404(()->conn.queryAsk("ASK{}"));
                 expect404(()->conn.fetch());
             }
-            
+
         } finally {
             server.stop();
         }
     }
 
     @Test public void stdServicesNoConfig() {
-        int port = WebLib.choosePort(); 
+        int port = WebLib.choosePort();
         FusekiServer server = server(port, "std-empty.ttl");
         String serverURL = "http://localhost:"+port+"/ds-no-ep";
-        
+
         server.start();
         try {
             try ( RDFConnection conn =  RDFConnectionFactory.connect(serverURL) ) {
@@ -170,51 +172,74 @@
             server.stop();
         }
     }
-    
-    @Test public void stdServicesGeneral() {
-        int port = WebLib.choosePort(); 
-        FusekiServer server = server(port, "std-general.ttl");
-        String serverURL = "http://localhost:"+port+"/ds";
-        
+
+    // Named services mirrored onto the dataset
+    @Test public void stdServicesOldStyle() {
+        int port = WebLib.choosePort();
+        FusekiServer server = server(port, "std-old-style.ttl");
+        String serverURL = "http://localhost:"+port+"/ds0";
+
         server.start();
         try {
-            // Either style.
-            
-            try ( RDFConnection conn =  RDFConnectionFactory.connect(serverURL) ) {
-                conn.update("INSERT DATA { <x:s> <x:p> 123 }");
-                conn.queryAsk("ASK{}");
-                Graph g = conn.fetch().getGraph();
-                assertEquals(1, g.size());
-            }
-            RDFConnectionRemoteBuilder builder = RDFConnectionRemote.create()
+            RDFConnectionRemoteBuilder builderUnamedServices = RDFConnectionRemote.create()
+                .destination(serverURL)
+                .updateEndpoint("")
+                .queryEndpoint("");
+            RDFConnectionRemoteBuilder builderNamedServices = RDFConnectionRemote.create()
                 .destination(serverURL)
                 .queryEndpoint("sparql")
                 .updateEndpoint("update")
                 .gspEndpoint("data");
-            try ( RDFConnection conn = builder.build() ) {
+
+            try ( RDFConnection conn = builderNamedServices.build() ) {
                 conn.update("INSERT DATA { <x:s> <x:p> 123 }");
+                Graph g = conn.fetch().getGraph();
+                assertEquals(1, g.size());
+            }
+
+            try ( RDFConnection conn = builderNamedServices.build() ) {
                 conn.queryAsk("ASK{}");
                 Graph g = conn.fetch().getGraph();
                 assertEquals(1, g.size());
             }
-            
+
+            try ( RDFConnection conn = builderUnamedServices.build() ) {
+                conn.update("INSERT DATA { <x:s> <x:p> 456 }");
+                conn.queryAsk("ASK{}");
+                Graph g = conn.fetch().getGraph();
+                assertEquals(2, g.size());
+            }
         } finally {
             server.stop();
         }
     }
 
-    private static String NL = "\n";
-    
+    @Test public void serverMisc() {
+        int port = WebLib.choosePort();
+        FusekiServer server = server(port, "server.ttl");
+        server.start();
+        try {
+            String x1 = HttpOp.execHttpGetString("http://localhost:"+port+"/$/ping");
+            assertNotNull(x1);
+            String x2 = HttpOp.execHttpGetString("http://localhost:"+port+"/$/stats");
+            assertNotNull(x2);
+            String x3 = HttpOp.execHttpGetString("http://localhost:"+port+"/$/metrics");
+            assertNotNull(x3);
+        } finally {
+            server.stop();
+        }
+    }
+
     @Test public void unionGraph1() {
         unionGraph("tdb1-endpoints.ttl","/ds-tdb1");
     }
-    
+
     @Test public void unionGraph2() {
         unionGraph("tdb2-endpoints.ttl","/ds-tdb2");
     }
-    
+
     @Test public void setupOpsSameName() {
-        int port = WebLib.choosePort(); 
+        int port = WebLib.choosePort();
         FusekiServer server = server(port, "setup1.ttl");
         String serverURL = "http://localhost:"+port+"/ds";
         server.start();
@@ -228,9 +253,9 @@
             server.stop();
         }
     }
-    
+
     @Test public void setupRootDataset() {
-        int port = WebLib.choosePort(); 
+        int port = WebLib.choosePort();
         FusekiServer server = server(port, "setup2.ttl");
         String serverURL = "http://localhost:"+port+"/";
         server.start();
@@ -245,12 +270,14 @@
         }
     }
 
+    private static String NL = "\n";
+
     private void unionGraph(String fnConfig, String dbName) {
-        int port = WebLib.choosePort(); 
+        int port = WebLib.choosePort();
         FusekiServer server = server(port, fnConfig);
         String serverURL = "http://localhost:"+port+dbName;
         server.start();
-        
+
         try {
             try ( RDFConnection conn =  RDFConnectionFactory.connect(serverURL) ) {
                 conn.update("INSERT DATA {"+NL+
@@ -281,18 +308,18 @@
             return x;
         }
     }
-    
-    
-    
+
+
+
     private static void assertCxtValue(RDFConnection conn, String contextSymbol, String value) {
-        String actual = 
+        String actual =
             conn.query(PREFIXES+"SELECT ?V { BIND(afn:context('"+contextSymbol+"') AS ?V) }")
             .execSelect()
             .nextBinding().get(Var.alloc("V"))
             .getLiteralLexicalForm();
         assertEquals(value, actual);
     }
-    
+
     private static void assertCxtValueNotNull(RDFConnection conn, String contextSymbol) {
         boolean b = conn.queryAsk(PREFIXES+"ASK { FILTER (afn:context('"+contextSymbol+"') != '' ) }");
         assertTrue(contextSymbol, b);
@@ -318,5 +345,5 @@
             .port(port)
             .build();
         return server;
-    }    
+    }
 }
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestEmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestEmbeddedFuseki.java
index d2debf8..20f0780 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestEmbeddedFuseki.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestEmbeddedFuseki.java
@@ -164,7 +164,7 @@
         } finally { server.stop(); }
     }
 
-    @Test public void embedded_05() {
+    @Test public void embedded_no_stats() {
         DatasetGraph dsg = dataset();
         int port = WebLib.choosePort();
         FusekiServer server = FusekiServer.create()
@@ -173,13 +173,33 @@
             .build();
         server.start();
         try {
-            // No stats
-            String x = HttpOp.execHttpGetString("http://localhost:"+port+"/$/stats");
-            assertNull(x);
+            // No server services
+            String x1 = HttpOp.execHttpGetString("http://localhost:"+port+"/$/ping");
+            assertNull(x1);
+
+            String x2 = HttpOp.execHttpGetString("http://localhost:"+port+"/$/stats");
+            assertNull(x2);
+
+            String x3 = HttpOp.execHttpGetString("http://localhost:"+port+"/$/metrics");
+            assertNull(x3);
         } finally { server.stop(); }
     }
 
-    @Test public void embedded_06() {
+    @Test public void embedded_ping() {
+        DatasetGraph dsg = dataset();
+        int port = WebLib.choosePort();
+        FusekiServer server = FusekiServer.create()
+            .port(port)
+            .add("/ds0", dsg)
+            .enablePing(true)
+            .build();
+        server.start();
+        String x = HttpOp.execHttpGetString("http://localhost:"+port+"/$/ping");
+        assertNotNull(x);
+        server.stop();
+    }
+
+    @Test public void embedded_stats() {
         DatasetGraph dsg = dataset();
         int port = WebLib.choosePort();
         FusekiServer server = FusekiServer.create()
@@ -193,16 +213,16 @@
         server.stop();
     }
 
-    @Test public void embedded_07() {
+    @Test public void embedded_metrics() {
         DatasetGraph dsg = dataset();
         int port = WebLib.choosePort();
         FusekiServer server = FusekiServer.create()
             .port(port)
             .add("/ds0", dsg)
-            .enablePing(true)
+            .enableMetrics(true)
             .build();
         server.start();
-        String x = HttpOp.execHttpGetString("http://localhost:"+port+"/$/ping");
+        String x = HttpOp.execHttpGetString("http://localhost:"+port+"/$/metrics");
         assertNotNull(x);
         server.stop();
     }
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomOperation.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomOperation.java
index a77d2b2..0afda07 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomOperation.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomOperation.java
@@ -1,4 +1,5 @@
-/*
+/*Multi
+ *
  * 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
@@ -18,15 +19,16 @@
 
 package org.apache.jena.fuseki.main;
 
+import static org.apache.jena.fuseki.main.FusekiTestLib.expectFail;
 import static org.junit.Assert.*;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.jena.atlas.io.IO;
+import org.apache.jena.atlas.lib.Lib;
 import org.apache.jena.atlas.web.HttpException;
 import org.apache.jena.atlas.web.TypedInputStream;
 import org.apache.jena.atlas.web.WebLib;
@@ -35,11 +37,9 @@
 import org.apache.jena.fuseki.build.FusekiExt;
 import org.apache.jena.fuseki.server.DataService;
 import org.apache.jena.fuseki.server.Operation;
+import org.apache.jena.fuseki.server.OperationRegistry;
 import org.apache.jena.fuseki.servlets.ActionService;
 import org.apache.jena.fuseki.servlets.HttpAction;
-import org.apache.jena.query.QueryExecution;
-import org.apache.jena.rdfconnection.RDFConnection;
-import org.apache.jena.rdfconnection.RDFConnectionFactory;
 import org.apache.jena.riot.WebContent;
 import org.apache.jena.riot.web.HttpOp;
 import org.apache.jena.sparql.core.DatasetGraph;
@@ -49,19 +49,21 @@
 
 /** Test for adding a new operation */
 public class TestFusekiCustomOperation {
-    private static final Operation newOp = Operation.alloc("http://example/special", "special", "Custom operation");
-    private static final String contentType = "application/special";
-    private static final String endpointName = "special";
+    private static final Operation newOp                 = Operation.alloc("http://example/special", "special", "Custom operation");
+    private static final String    contentType           = "application/special";
+    private static final String    endpointName          = "special";
+    private static final String    customHandlerBodyGet  = "    ** Hello world (GET) ** custom handler **";
+    private static final String    customHandlerBodyPost = "    ** Hello world (POST) ** custom handler **";
 
-    private final ActionService customHandler = new CustomTestService() {
+    private final ActionService    customHandler =
+        new CustomTestService() {
         @Override
         protected void doGet(HttpAction action) {
             action.response.setStatus(HttpSC.OK_200);
             try {
                 action.response.setContentType(WebContent.contentTypeTextPlain);
-                action.response.getOutputStream().println("    ** Hello world (GET) **");
-            }
-            catch (IOException e) {
+                action.response.getOutputStream().print(customHandlerBodyGet);
+            } catch (IOException e) {
                 e.printStackTrace();
             }
         }
@@ -77,16 +79,16 @@
             action.response.setStatus(HttpSC.OK_200);
             try {
                 action.response.setContentType(WebContent.contentTypeTextPlain);
-                action.response.getOutputStream().println("    ** Hello world (POST) **");
-            }
-            catch (IOException e) {
+                action.response.getOutputStream().print(customHandlerBodyPost);
+            } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     };
 
-    private final int port = WebLib.choosePort();
-    private final String url = "http://localhost:"+port;
+    private final int              port                  = WebLib.choosePort();
+    // Without trailing "/"
+    private final String           url                   = "http://localhost:" + port;
 
     @Test
     public void cfg_dataservice_named() {
@@ -95,16 +97,19 @@
         DatasetGraph dsg = DatasetGraphFactory.createTxnMem();
         DataService dataService = new DataService(dsg);
         FusekiConfig.populateStdServices(dataService, true);
-        FusekiExt.registerOperation(newOp, customHandler);
-        FusekiConfig.addServiceEP(dataService, newOp, endpointName);
+        try {
+            FusekiExt.registerOperation(newOp, customHandler);
+            assertTrue(OperationRegistry.get().isRegistered(newOp));
 
-        FusekiServer server =
-            FusekiServer.create()
-                .port(port)
-                .registerOperation(newOp, contentType, customHandler)
-                .add("/ds", dataService)
-                .build();
-        testServer(server, url, endpointName, true);
+            FusekiConfig.addServiceEP(dataService, newOp, endpointName);
+            FusekiServer server = FusekiServer.create().port(port).registerOperation(newOp, contentType, customHandler)
+                .add("/ds", dataService).build();
+            testServer(server, url, endpointName, true, false);
+        }
+        finally {
+            FusekiExt.unregisterOperation(newOp, customHandler);
+        }
+        assertFalse(OperationRegistry.get().isRegistered(newOp));
     }
 
     @Test
@@ -114,149 +119,136 @@
         DatasetGraph dsg = DatasetGraphFactory.createTxnMem();
         DataService dataService = new DataService(dsg);
         FusekiConfig.populateStdServices(dataService, true);
-        FusekiExt.registerOperation(newOp, customHandler);
-        FusekiConfig.addServiceEP(dataService, newOp, null);
+        try {
+            FusekiExt.registerOperation(newOp, customHandler);
+            assertTrue(OperationRegistry.get().isRegistered(newOp));
 
-        FusekiServer server =
-            FusekiServer.create()
-                .port(port)
-                .registerOperation(newOp, contentType, customHandler)
-                .add("/ds", dataService)
-                .build();
-        // No endpoint name dispatch - content-type required.
-        testServer(server, url, null, true);
-    }
-
-    @Test
-    public void cfg_builder_CT_named() {
-        FusekiServer server =
-            FusekiServer.create()
-                .port(port)
-                .registerOperation(newOp, contentType, customHandler)
-                .add("/ds", DatasetGraphFactory.createTxnMem(), true)
-                .addEndpoint("/ds", endpointName, newOp)
-                .build();
-        testServer(server, url, endpointName, true);
-    }
-
-    @Test
-    public void cfg_builder_CT_noName() {
-        FusekiServer server =
-            FusekiServer.create()
-                .port(port)
-                .registerOperation(newOp, contentType, customHandler)
-                .add("/ds", DatasetGraphFactory.createTxnMem(), true)
-                .addEndpoint("/ds", "", newOp)
-                .build();
-        testServer(server, url, null, true);
+            FusekiConfig.addServiceEP(dataService, newOp, null);
+            FusekiServer server = FusekiServer.create().port(port).registerOperation(newOp, contentType, customHandler)
+                .add("/ds", dataService).build();
+            // No endpoint name dispatch - content-type required.
+            testServer(server, url, "", false, true);
+        }
+        finally {
+            FusekiExt.unregisterOperation(newOp, customHandler);
+        }
+        assertFalse(OperationRegistry.get().isRegistered(newOp));
     }
 
     @Test
     public void cfg_builder_noCT() {
-        FusekiServer server =
-            FusekiServer.create()
-                .port(port)
-                .registerOperation(newOp, null, customHandler)
-                .add("/ds", DatasetGraphFactory.createTxnMem(), true)
-                .addEndpoint("/ds", endpointName, newOp)
-                .build();
-        testServer(server, url, endpointName, false);
+        // Register operation in the builder. Dispatch by-name. CT not required.
+        FusekiServer server = FusekiServer.create().port(port).registerOperation(newOp, null, customHandler)
+            .add("/ds", DatasetGraphFactory.createTxnMem(), true).addEndpoint("/ds", endpointName, newOp).build();
+        testServer(server, url, endpointName, true, false);
     }
 
-    @Test(expected=FusekiConfigException.class)
+    @Test
+    public void cfg_builder_CT_named() {
+        FusekiServer server = FusekiServer.create().port(port).registerOperation(newOp, contentType, customHandler)
+            .add("/ds", DatasetGraphFactory.createTxnMem(), true).addEndpoint("/ds", endpointName, newOp).build();
+        // Endpoint name dispatch - with content-type
+        testServer(server, url, endpointName, true, true);
+    }
+
+    @Test
+    public void cfg_builder_CT_noName() {
+        // Register operation in the builder. Dispatch by-content-type on "".
+        FusekiServer server = FusekiServer.create().port(port).registerOperation(newOp, contentType, customHandler)
+            .add("/ds", DatasetGraphFactory.createTxnMem(), true).addEndpoint("/ds", "", newOp).build();
+        testServer(server, url, "", false, true);
+    }
+
+    @Test(expected = FusekiConfigException.class)
     public void cfg_bad_01() {
-        FusekiServer.create()
-        .port(port)
-        .registerOperation(newOp, null, customHandler)
-        .addEndpoint("/UNKNOWN", endpointName, newOp);
-        //.build();
+        FusekiServer.create().port(port).registerOperation(newOp, null, customHandler).addEndpoint("/UNKNOWN", endpointName, newOp);
+        // .build();
     }
 
-    @Test(expected=FusekiConfigException.class)
+    @Test(expected = FusekiConfigException.class)
     public void cfg_bad_02() {
-        FusekiServer.create()
-        .port(port)
-        //.registerOperation(newOp, null, customHandler)
-        .add("/ds", DatasetGraphFactory.createTxnMem(), true)
-        // Unregistered.
-        .addEndpoint("/ds", endpointName, newOp);
-        //.build();
-    }
-
-    public void cfg_bad_ct_not_enabled_here() {
-        FusekiServer server = FusekiServer.create()
-            .port(port)
-            .registerOperation(newOp, "app/special", customHandler)
+        FusekiServer.create().port(port)
+            // .registerOperation(newOp, null, customHandler)
             .add("/ds", DatasetGraphFactory.createTxnMem(), true)
             // Unregistered.
-            .addEndpoint("/ds", endpointName, newOp)
-            .build();
-        testServer(server, url, null, false);
+            .addEndpoint("/ds", endpointName, newOp);
+        // .build();
     }
 
+    // Bad test: no MIME type must match.
+    @Test
+    public void cfg_bad_ct_not_enabled_here_1() {
+        FusekiServer server = FusekiServer.create().port(port)
+            // Wrong MIME type.
+            .registerOperation(newOp, "app/special", customHandler).add("/ds", DatasetGraphFactory.createTxnMem(), true)
+            // Unregistered CT dispatch.
+            .addEndpoint("/ds", "", newOp).build();
 
-    private static void testServer(FusekiServer server, String url, String epName, boolean withContentType) {
+        // CT dispatch.
+        expectFail(() -> testServer(server, url, "", false, true), HttpSC.Code.BAD_REQUEST);
+    }
+
+    /** Call the server to check that the endpoint can be contacted. */
+    private static void testServer(FusekiServer server, String url, String epName, boolean withoutContentType, boolean withContentType) {
         try {
             server.start();
-            // Try query (no extension required)
-            try(RDFConnection rconn = RDFConnectionFactory.connect(url+"/ds")) {
-                try(QueryExecution qExec = rconn.query("ASK {}")) {
-                    qExec.execAsk();
-                }
+            Lib.sleep(100);
+
+            String svcCall = StringUtils.isEmpty(epName) ? url + "/ds" : url + "/ds/" + epName;
+
+            if ( withoutContentType )
+                testServerNoCT(server, svcCall);
+
+            if ( withContentType )
+                testServerCT(server, svcCall);
+
+            // DELETE -> fails
+            int statusCode = expectFail(() -> HttpOp.execHttpDelete(svcCall));
+            switch (statusCode) {
+                // Acceptable.
+                case HttpSC.BAD_REQUEST_400 :
+                case HttpSC.METHOD_NOT_ALLOWED_405 :
+                    break;
+                default :
+                    // Wrong
+                    fail("Status code = " + statusCode);
             }
-
-            if ( epName != null ) {
-                if ( ! epName.isEmpty() ) {
-                    // Service endpoint name : GET
-                    String svcCall = url+"/ds/"+epName;
-
-                    String s1 = HttpOp.execHttpGetString(svcCall);
-
-                    // Service endpoint name : POST
-                    try ( TypedInputStream stream = HttpOp.execHttpPostStream(svcCall, "ignored", "", "text/plain") ) {
-                        assertNotNull(stream);
-                        String x = IOUtils.toString(stream, StandardCharsets.UTF_8);
-                        assertNotNull(x);
-                    } catch (IOException ex) {
-                        IO.exception(ex);
-                    }
-                }
-            } else {
-                // No endpoint so we expect a 404.
-                try {
-                    // Service endpoint name : GET
-                    HttpOp.execHttpGet(url+"/ds/"+endpointName);
-                    fail("Expected to fail HTTP GET");
-                } catch (HttpException ex) {
-                    assertEquals(404, ex.getStatusCode());
-                }
-            }
-
-            if ( withContentType ) {
-                // Content-type
-                try ( TypedInputStream stream = HttpOp.execHttpPostStream(url+"/ds", contentType, "", "text/plain") ) {
-                    assertNotNull(stream);
-                    String x = IOUtils.toString(stream, StandardCharsets.UTF_8);
-                    assertNotNull(x);
-                } catch (IOException ex) {
-                    IO.exception(ex);
-                }
-            } else {
-                // No Content-Type
-                try ( TypedInputStream stream = HttpOp.execHttpPostStream(url+"/ds", contentType, "", "text/plain") ) {
-                    fail("Expected to fail HTTP POST using Content-Type");
-                } catch (HttpException ex) {}
-
-                // Service endpoint name. DELETE -> fails 405
-                try {
-                    HttpOp.execHttpDelete(url+"/ds/"+endpointName);
-                    throw new IllegalStateException("DELETE succeeded");
-                } catch (HttpException ex) {
-                    assertEquals(405, ex.getStatusCode());
-                }
-            }
-        } finally {
+        }
+        finally {
             server.stop();
         }
     }
+
+    private static void testServerCT(FusekiServer server, String svcCall) {
+        // Content-type
+        try (TypedInputStream stream = HttpOp.execHttpPostStream(svcCall, contentType, "", "text/plain")) {
+            assertNotNull(stream);
+            String x = IOUtils.toString(stream, StandardCharsets.UTF_8);
+            assertValidResponseBody(customHandlerBodyPost, x);
+        } catch (IOException ex) {
+            IO.exception(ex);
+        }
+    }
+
+    private static void testServerNoCT(FusekiServer server, String svcCall) {
+        // Service endpoint name : GET
+        String s1 = HttpOp.execHttpGetString(svcCall);
+        if ( s1 == null )
+            throw new HttpException(HttpSC.NOT_FOUND_404, "Not Found", "");
+        assertValidResponseBody(customHandlerBodyGet, s1);
+
+        // Service endpoint name : POST
+        try (TypedInputStream stream = HttpOp.execHttpPostStream(svcCall, "ignored", "", "text/plain")) {
+            assertNotNull(stream);
+            String x = IOUtils.toString(stream, StandardCharsets.UTF_8);
+            assertValidResponseBody(customHandlerBodyPost, x);
+        } catch (IOException ex) {
+            IO.exception(ex);
+        }
+    }
+
+    private static void assertValidResponseBody(String expectedResponseBody, String responseBody) {
+        assertNotNull(responseBody);
+        assertEquals(expectedResponseBody, responseBody);
+    }
 }
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmd.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmd.java
index 1c0d4dd..dd958d8 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmd.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmd.java
@@ -82,4 +82,10 @@
         assertNotNull(x);
         JSON.parse(x);
     }
+
+    @Test public void metrics_01() {
+        server("--mem", "--metrics", "/ds");
+        String x = HttpOp.execHttpGetString(serverURL+"/$/metrics");
+        assertNotNull(x);
+    }
 }
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHTTP.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHTTP.java
index 5edf601..95ccf2a 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHTTP.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHTTP.java
@@ -181,7 +181,7 @@
     // Servlet - mounted at /ds/myServlet, but not a service that Fuseki dispatches.
     @Test public void plainServlet() {
         String x = HttpOp.execHttpGetString(URL+"/myServlet");
-        assertEquals(x, "SERVLET");
+        assertEquals("SERVLET",x);
     }
 
     // Files - a static file /ds/file.txt is visible.
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security-shared.ttl b/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security-shared.ttl
index 210ff9e..034ee31 100644
--- a/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security-shared.ttl
+++ b/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security-shared.ttl
@@ -71,7 +71,7 @@
 
 ## Shared database.
 <#tdb_dataset_shared> rdf:type      tdb2:DatasetTDB2 ;
-    tdb2:location "--mem--/DB" ;
+    tdb2:location "--mem--" ;
     tdb2:unionDefaultGraph true ;
     .
 
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/server.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/server.ttl
new file mode 100644
index 0000000..73de85c
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/testing/Config/server.ttl
@@ -0,0 +1,26 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:services   ( <#service1> ) ;
+   fuseki:pingEP     true ;
+   fuseki:statsEP    true ;
+   fuseki:metricsEP  true ;
+.
+
+<#service1> rdf:type fuseki:Service ;
+    fuseki:name         "ds" ;
+    fuseki:endpoint     [
+       fuseki:operation  fuseki:query;
+       fuseki:name       "" ;
+    ] ;
+    fuseki:dataset      <#emptyDataset> ;
+.
+
+<#emptyDataset> rdf:type ja:RDFDataset .
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/setup2.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/setup2.ttl
index 19201d2..16171b1 100644
--- a/jena-fuseki2/jena-fuseki-main/testing/Config/setup2.ttl
+++ b/jena-fuseki2/jena-fuseki-main/testing/Config/setup2.ttl
@@ -15,7 +15,7 @@
     ## Same endpoint name, different operations available.
     fuseki:endpoint     [
        fuseki:operation  fuseki:query;
-       ## Named service and root daatset not supported.
+       ## Named service and root dataset not supported.
        #fuseki:name       "sparql" ;
     ] ;
     fuseki:endpoint     [
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/std-general.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/std-general.ttl
deleted file mode 100644
index a2cffef..0000000
--- a/jena-fuseki2/jena-fuseki-main/testing/Config/std-general.ttl
+++ /dev/null
@@ -1,24 +0,0 @@
-## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
-
-PREFIX :        <#>
-PREFIX fuseki:  <http://jena.apache.org/fuseki#>
-PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
-
-PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
-PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
-
-[] rdf:type fuseki:Server .
-
-<#service1> rdf:type fuseki:Service ;
-    fuseki:name         "ds" ;
-    fuseki:endpoint [ fuseki:operation fuseki:query;  fuseki:name "sparql"  ] ;
-    fuseki:endpoint [ fuseki:operation fuseki:query;  fuseki:name "query"   ] ;
-    fuseki:endpoint [ fuseki:operation fuseki:update; fuseki:name "update"  ] ;
-    fuseki:endpoint [ fuseki:operation fuseki:gsp_r;  fuseki:name "get"     ] ;
-    fuseki:endpoint [ fuseki:operation fuseki:gsp_rw; fuseki:name "data"    ] ;
-
-    ## No blocked on endpoint "".
-    fuseki:dataset      <#emptyDataset> ;
-    .
-
-<#emptyDataset> rdf:type ja:RDFDataset .
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/std-named.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/std-named.ttl
index a465abd..b36fe0b 100644
--- a/jena-fuseki2/jena-fuseki-main/testing/Config/std-named.ttl
+++ b/jena-fuseki2/jena-fuseki-main/testing/Config/std-named.ttl
@@ -18,10 +18,6 @@
     fuseki:endpoint [ fuseki:operation fuseki:gsp_r;  fuseki:name "get"     ] ;
     fuseki:endpoint [ fuseki:operation fuseki:gsp_rw; fuseki:name "data"    ] ;
 
-    ## Any explicit dataset level operation means that
-    ## trying as named service does not happen.
-    fuseki:endpoint [ fuseki:operation fuseki:no_op ] ;
-    
     fuseki:dataset      <#emptyDataset> ;
     .
 
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/std-old-style.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/std-old-style.ttl
new file mode 100644
index 0000000..393f0d3
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/testing/Config/std-old-style.ttl
@@ -0,0 +1,23 @@
+## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
+
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+
+[] rdf:type fuseki:Server .
+
+<#service1> rdf:type fuseki:Service ;
+    fuseki:name                        "ds0" ;
+    fuseki:serviceQuery                "query";
+    fuseki:serviceQuery                "sparql";
+    fuseki:serviceUpdate               "update";
+    ##fuseki:serviceUpload               "upload" ;
+    fuseki:serviceReadWriteGraphStore  "data" ;
+    fuseki:serviceReadGraphStore       "get" ;
+    fuseki:dataset                     <#dataset> ;
+    .
+
+<#dataset> rdf:type ja:RDFDataset .
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/JettyFusekiWebapp.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/JettyFusekiWebapp.java
index b1086a8..420d362 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/JettyFusekiWebapp.java
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/JettyFusekiWebapp.java
@@ -199,8 +199,8 @@
         // which happens during server startup.
         // This the name of the ServletContext logger as well
         webapp.setDisplayName(Fuseki.servletRequestLogName);
-        webapp.setParentLoaderPriority(true);  // Normal Java classloader behaviour.
-        webapp.setErrorHandler(new FusekiErrorHandler());
+        webapp.setParentLoaderPriority(true);               // Normal Java classloader behaviour.
+        webapp.setErrorHandler(new FusekiErrorHandler());   // If used.
         return webapp;
     }
 
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-mem b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-mem
index 6ce86d0..6f42e59 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-mem
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-mem
@@ -22,8 +22,6 @@
     fuseki:serviceUpload               "upload" ;
     fuseki:serviceReadWriteGraphStore  "data" ;     
     fuseki:serviceReadGraphStore       "get" ;
-    fuseki:serviceReadQuads            "" ;
-    fuseki:serviceReadWriteQuads       "" ;
     fuseki:dataset                     <#dataset> ;
     .
 
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb
index a24655d..9b18588 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb
@@ -22,8 +22,6 @@
     fuseki:serviceReadWriteGraphStore  "data" ;
     # A separate read-only graph store endpoint:
     fuseki:serviceReadGraphStore       "get" ;
-    fuseki:serviceReadQuads            "";
-    fuseki:serviceReadWriteQuads       "";
     fuseki:dataset                     <#tdb_dataset_readwrite> ;
     .
 
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb-dir b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb-dir
index 338d7de..f22de07 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb-dir
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb-dir
@@ -21,8 +21,6 @@
     fuseki:serviceUpload               "upload" ;
     fuseki:serviceReadWriteGraphStore  "data" ;     
     fuseki:serviceReadGraphStore       "get" ;
-    fuseki:serviceReadQuads            "" ;
-    fuseki:serviceReadWriteQuads       "" ;
     fuseki:dataset                     <#tdb_dataset_readwrite> ;
     .
 
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb-mem b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb-mem
index f751a69..6b9c656 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb-mem
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb-mem
@@ -21,8 +21,6 @@
     fuseki:serviceUpload               "upload" ;
     fuseki:serviceReadWriteGraphStore  "data" ;     
     fuseki:serviceReadGraphStore       "get" ;
-    fuseki:serviceReadQuads            "" ;
-    fuseki:serviceReadWriteQuads       "" ;
     fuseki:dataset                     <#tdb_dataset_readwrite> ;
     .
 
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2 b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2
index c3412fc..3fa7c16 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2
@@ -21,8 +21,6 @@
     fuseki:serviceUpload               "upload" ;
     fuseki:serviceReadWriteGraphStore  "data" ;     
     fuseki:serviceReadGraphStore       "get" ;
-    fuseki:serviceReadQuads            "" ;
-    fuseki:serviceReadWriteQuads       "" ;
     fuseki:dataset                     <#tdb_dataset_readwrite> ;
     
     .
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2-dir b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2-dir
index 680d0a8..3312b93 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2-dir
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2-dir
@@ -21,8 +21,6 @@
     fuseki:serviceUpload               "upload" ;
     fuseki:serviceReadWriteGraphStore  "data" ;     
     fuseki:serviceReadGraphStore       "get" ;
-    fuseki:serviceReadQuads            "" ;
-    fuseki:serviceReadWriteQuads       "" ;
     fuseki:dataset                     <#tdb_dataset_readwrite> ;
     
     .
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2-mem b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2-mem
index d9b21a8..de362d0 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2-mem
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/resources/org/apache/jena/fuseki/webapp/templates/config-tdb2-mem
@@ -21,8 +21,6 @@
     fuseki:serviceUpload               "upload" ;
     fuseki:serviceReadWriteGraphStore  "data" ;     
     fuseki:serviceReadGraphStore       "get" ;
-    fuseki:serviceReadQuads            "" ;
-    fuseki:serviceReadWriteQuads       "" ;
     fuseki:dataset                     <#tdb_dataset_readwrite> ;
     .
 
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/test/java/org/apache/jena/fuseki/TestServerReadOnly.java b/jena-fuseki2/jena-fuseki-webapp/src/test/java/org/apache/jena/fuseki/TestServerReadOnly.java
index 3bdf3d8..d61f026 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/test/java/org/apache/jena/fuseki/TestServerReadOnly.java
+++ b/jena-fuseki2/jena-fuseki-webapp/src/test/java/org/apache/jena/fuseki/TestServerReadOnly.java
@@ -25,6 +25,7 @@
 
 import org.apache.http.HttpEntity;
 import org.apache.http.entity.StringEntity;
+import org.apache.jena.atlas.web.TypedInputStream;
 import org.apache.jena.fuseki.test.FusekiTest;
 import org.apache.jena.query.Query;
 import org.apache.jena.query.QueryExecution;
@@ -99,6 +100,13 @@
     }
 
     @Test
+    public void dataset_readonly_GET() {
+        // Try to read
+        try ( TypedInputStream in = HttpOp.execHttpGet(ServerCtl.urlDataset()) ) {}
+    }
+
+
+    @Test
     public void dataset_w_readonly_POST() {
         // Try to write
         FusekiTest.execWithHttpException(HttpSC.METHOD_NOT_ALLOWED_405, ()->{
diff --git a/jena-fuseki2/pom.xml b/jena-fuseki2/pom.xml
index 12bbea0..2af616e 100644
--- a/jena-fuseki2/pom.xml
+++ b/jena-fuseki2/pom.xml
@@ -65,6 +65,8 @@
     <module>jena-fuseki-war</module>
     <module>jena-fuseki-fulljar</module>
 
+    <module>jena-fuseki-docker</module>
+
     <module>apache-jena-fuseki</module>
   </modules>
   
diff --git a/jena-rdfconnection/src/main/java/org/apache/jena/rdfconnection/LibRDFConn.java b/jena-rdfconnection/src/main/java/org/apache/jena/rdfconnection/LibRDFConn.java
index b4a9b42..51780f1 100644
--- a/jena-rdfconnection/src/main/java/org/apache/jena/rdfconnection/LibRDFConn.java
+++ b/jena-rdfconnection/src/main/java/org/apache/jena/rdfconnection/LibRDFConn.java
@@ -43,6 +43,15 @@
         return graphStoreProtocolService + queryStringForGraph(ch, graphName) ;
     }
 
+    /**
+     * Service endpoint URL calculation.
+     * <ul>
+     * <li> If srvEndpoint is null,  "destination"
+     * <li> If srvEndpoint is "",  "destination"
+     * <li> If srvEndpoint is an absolute URL,  "srvEndpoint"
+     * <li> "destination / srvEndpoint" (ensures the "/"), while preserving the query string
+     * </ul>
+     */
     /*package*/ static String formServiceURL(String destination, String srvEndpoint) {
         if ( srvEndpoint == null )
             return null;
diff --git a/jena-shacl/shaclc/shaclc.jj b/jena-shacl/shaclc/shaclc.jj
index e81af72..cb1e041 100644
--- a/jena-shacl/shaclc/shaclc.jj
+++ b/jena-shacl/shaclc/shaclc.jj
@@ -140,7 +140,11 @@
 void constraint() : { }
 {
   { startConstraint(); }
-  ( (nodeOr())+ | propertyShape() )
+  ( (nodeOr())+ | propertyShape()
+// <EXT>
+    | shapeRef(false)
+// </EXT>
+  )
   { finishConstraint() ; }
   <DOT>
 }
@@ -217,7 +221,7 @@
 void propertyAtom() : { }
 {
   // Work on currentPropertyShape()
-  propertyType() | nodeKind() | shapeRef() | propertyValue() |
+  propertyType() | nodeKind() | shapeRef(true) | propertyValue() |
      ( { startNestedPropertyAtom(); } nodeShapeBody() { finishNestedPropertyAtom(); })
 }
 
@@ -249,7 +253,7 @@
   { rNodeKind(t.image); }
 }
 
-void shapeRef() : { Token t; String iriStr; }
+void shapeRef(boolean inPropertyShape) : { Token t; String iriStr; }
 {
   // consistent WS handling.?
   ( t = <ATPNAME_LN>
@@ -258,8 +262,7 @@
   { iriStr = resolvePName(t.image.substring(1), t.beginLine, t.beginColumn) ; }
   | <AT> iriStr= IRIREF()
   )
-  //<AT> iriStr = iri()
-  { rShapeRef(iriStr); }
+  { rShapeRef(inPropertyShape, iriStr); }
 }
 
 void propertyValue() : { String s; Node n; List<Node> x; }
@@ -305,6 +308,11 @@
     t = "equals" | t = "disjoint" | t = "lessThan" | t = "lessThanOrEquals" |
     t = "qualifiedValueShape" | t = "qualifiedMinCount" | t = "qualifiedMaxCount" | t = "qualifiedValueShapesDisjoint" |
     t = "closed" | t = "ignoredProperties" | t = "hasValue" | t = "in"
+// <EXT>
+        | t = "group" | t = "order"
+        | t = "name"  | t = "description"
+        | t = "defaultValue"
+// </EXT>
   )
   { return t.image; }
 }
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/ShaclCompactParser.java b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/ShaclCompactParser.java
index 4e08482..11a05e1 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/ShaclCompactParser.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/ShaclCompactParser.java
@@ -426,9 +426,13 @@
 
     // shapeRef: Produce a triple ?property sh:node ?node where ?node is the IRI
     // derived from the substring of shapeRef after the '@' character using iri.
-    protected void rShapeRef(String iriStr) {
+    protected void rShapeRef(boolean inPropertyShape, String iriStr) {
         Node x = iri(iriStr);
-        triple(currentTripleAcc(), currentPropertyShape(), SHACL.node, x);
+        if ( inPropertyShape )
+            triple(currentTripleAcc(), currentPropertyShape(), SHACL.node, x);
+        else
+            // Extension.
+            triple(currentTripleAcc(), currentNodeShape(), SHACL.node, x);
     }
 
     // propertyValue: Produce a triple ?property ?predicate ?object where ?predicate
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJ.java b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJ.java
index 1cce249..9ef855f 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJ.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJ.java
@@ -210,12 +210,15 @@
       case 31:
       case 32:
       case 33:
+      case AT:
       case CARAT:
       case BANG:
       case LPAREN:
       case IRIref:
       case PNAME_NS:
-      case PNAME_LN:{
+      case PNAME_LN:
+      case ATPNAME_NS:
+      case ATPNAME_LN:{
         ;
         break;
         }
@@ -306,6 +309,12 @@
       propertyShape();
       break;
       }
+    case AT:
+    case ATPNAME_NS:
+    case ATPNAME_LN:{
+      shapeRef(false);
+      break;
+      }
     default:
       jj_la1[9] = jj_gen;
       jj_consume_token(-1);
@@ -563,7 +572,7 @@
     case AT:
     case ATPNAME_NS:
     case ATPNAME_LN:{
-      shapeRef();
+      shapeRef(true);
       break;
       }
     case 9:
@@ -680,7 +689,7 @@
 rNodeKind(t.image);
   }
 
-  final public void shapeRef() throws ParseException {Token t; String iriStr;
+  final public void shapeRef(boolean inPropertyShape) throws ParseException {Token t; String iriStr;
     switch ((jj_ntk==-1)?jj_ntk_f():jj_ntk) {
     case ATPNAME_LN:{
       t = jj_consume_token(ATPNAME_LN);
@@ -702,7 +711,7 @@
       jj_consume_token(-1);
       throw new ParseException();
     }
-rShapeRef(iriStr);
+rShapeRef(inPropertyShape, iriStr);
   }
 
   final public void propertyValue() throws ParseException {String s; Node n; List<Node> x;
@@ -968,6 +977,26 @@
       t = jj_consume_token(33);
       break;
       }
+    case 41:{
+      t = jj_consume_token(41);
+      break;
+      }
+    case 42:{
+      t = jj_consume_token(42);
+      break;
+      }
+    case 43:{
+      t = jj_consume_token(43);
+      break;
+      }
+    case 44:{
+      t = jj_consume_token(44);
+      break;
+      }
+    case 45:{
+      t = jj_consume_token(45);
+      break;
+      }
     default:
       jj_la1[23] = jj_gen;
       jj_consume_token(-1);
@@ -1284,7 +1313,7 @@
     lex = string();
 String lang = null ; String dt = null ;
     switch ((jj_ntk==-1)?jj_ntk_f():jj_ntk) {
-    case 41:
+    case 46:
     case LANGTAG:{
       switch ((jj_ntk==-1)?jj_ntk_f():jj_ntk) {
       case LANGTAG:{
@@ -1292,8 +1321,8 @@
 lang = stripChars(t.image, 1) ;
         break;
         }
-      case 41:{
-        jj_consume_token(41);
+      case 46:{
+        jj_consume_token(46);
         dt = datatype();
         break;
         }
@@ -1421,13 +1450,13 @@
       jj_la1_0 = new int[] {0x0,0x0,0x0,0x0,0x0,0x2,0x0,0xfffffe00,0xfffffe00,0xfffffe00,0x0,0x0,0x0,0xfffffff8,0xfffffff8,0x0,0x0,0xfffffff8,0x0,0x1f8,0x0,0x0,0xfffffe00,0xffffe000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,};
    }
    private static void jj_la1_init_1() {
-      jj_la1_1 = new int[] {0x10000,0xe0000,0x300000,0x300000,0xe0000,0x0,0x0,0xa0000003,0x80000003,0xa0000003,0x8000000,0x80000000,0xc00000,0x90000003,0x90000003,0x8000000,0x80000000,0x10000003,0x0,0x0,0x10000000,0xc00000,0x3,0x1ff,0x8000000,0x0,0x2000000,0x20000000,0x2000000,0x0,0xc00000,0xc00000,0xc00000,0xc00000,0x0,0x200,0x200,0x0,0x0,0x0,};
+      jj_la1_1 = new int[] {0x200000,0x1c00000,0x6000000,0x6000000,0x1c00000,0x0,0x0,0x3,0x3,0x3,0x0,0x0,0x18000000,0x3,0x3,0x0,0x0,0x3,0x0,0x0,0x0,0x18000000,0x3,0x3fff,0x0,0x0,0x40000000,0x0,0x40000000,0x0,0x18000000,0x18000000,0x18000000,0x18000000,0x0,0x4000,0x4000,0x0,0x0,0x0,};
    }
    private static void jj_la1_init_2() {
-      jj_la1_2 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x3810,0x0,0x3810,0x0,0x0,0x7783900,0xf940,0xf940,0x0,0x0,0xf840,0x1000004,0x0,0xc000,0x7783900,0x0,0x0,0x0,0x2,0x5,0x3810,0x5,0x3810,0x7783800,0x7783800,0x7780000,0x0,0x7000000,0x10000000,0x10000000,0x780000,0x3800,0x3000,};
+      jj_la1_2 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x70000,0x1f0216,0x10,0x1f0216,0x1,0x10,0xef072000,0x1f2812,0x1f2812,0x1,0x10,0x1f0802,0x20000080,0x0,0x180002,0xef072000,0x0,0x0,0x1,0x40,0xa0,0x70204,0xa0,0x70200,0xef070000,0xef070000,0xef000000,0x0,0xe0000000,0x0,0x0,0xf000000,0x70000,0x60000,};
    }
    private static void jj_la1_init_3() {
-      jj_la1_3 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,};
+      jj_la1_3 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x2,0x0,0x0,0x0,};
    }
 
   /** Constructor with InputStream. */
@@ -1544,7 +1573,7 @@
   /** Generate ParseException. */
   public ParseException generateParseException() {
     jj_expentries.clear();
-    boolean[] la1tokens = new boolean[105];
+    boolean[] la1tokens = new boolean[110];
     if (jj_kind >= 0) {
       la1tokens[jj_kind] = true;
       jj_kind = -1;
@@ -1567,7 +1596,7 @@
         }
       }
     }
-    for (int i = 0; i < 105; i++) {
+    for (int i = 0; i < 110; i++) {
       if (la1tokens[i]) {
         jj_expentry = new int[1];
         jj_expentry[0] = i;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJConstants.java b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJConstants.java
index 6cb2aa1..468f975 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJConstants.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJConstants.java
@@ -29,121 +29,121 @@
   /** End of File. */
   int EOF = 0;
   /** RegularExpression Id. */
-  int SINGLE_LINE_COMMENT = 47;
+  int SINGLE_LINE_COMMENT = 52;
   /** RegularExpression Id. */
-  int BOM = 48;
+  int BOM = 53;
   /** RegularExpression Id. */
-  int BASE = 49;
+  int BASE = 54;
   /** RegularExpression Id. */
-  int IMPORTS = 50;
+  int IMPORTS = 55;
   /** RegularExpression Id. */
-  int PREFIX = 51;
+  int PREFIX = 56;
   /** RegularExpression Id. */
-  int SHAPE_CLASS = 52;
+  int SHAPE_CLASS = 57;
   /** RegularExpression Id. */
-  int SHAPE = 53;
+  int SHAPE = 58;
   /** RegularExpression Id. */
-  int TRUE = 54;
+  int TRUE = 59;
   /** RegularExpression Id. */
-  int FALSE = 55;
+  int FALSE = 60;
   /** RegularExpression Id. */
-  int HEX = 56;
+  int HEX = 61;
   /** RegularExpression Id. */
-  int PLUS = 57;
+  int PLUS = 62;
   /** RegularExpression Id. */
-  int MINUS = 58;
+  int MINUS = 63;
   /** RegularExpression Id. */
-  int VBAR = 59;
+  int VBAR = 64;
   /** RegularExpression Id. */
-  int AT = 60;
+  int AT = 65;
   /** RegularExpression Id. */
-  int CARAT = 61;
+  int CARAT = 66;
   /** RegularExpression Id. */
-  int DOT = 62;
+  int DOT = 67;
   /** RegularExpression Id. */
-  int BANG = 63;
+  int BANG = 68;
   /** RegularExpression Id. */
-  int QMARK = 64;
+  int QMARK = 69;
   /** RegularExpression Id. */
-  int SLASH = 65;
+  int SLASH = 70;
   /** RegularExpression Id. */
-  int STAR = 66;
+  int STAR = 71;
   /** RegularExpression Id. */
-  int EQUALS = 67;
+  int EQUALS = 72;
   /** RegularExpression Id. */
-  int LPAREN = 68;
+  int LPAREN = 73;
   /** RegularExpression Id. */
-  int RPAREN = 69;
+  int RPAREN = 74;
   /** RegularExpression Id. */
-  int LBRACE = 70;
+  int LBRACE = 75;
   /** RegularExpression Id. */
-  int RBRACE = 71;
+  int RBRACE = 76;
   /** RegularExpression Id. */
-  int LBRACKET = 72;
+  int LBRACKET = 77;
   /** RegularExpression Id. */
-  int RBRACKET = 73;
+  int RBRACKET = 78;
   /** RegularExpression Id. */
-  int UCHAR = 74;
+  int UCHAR = 79;
   /** RegularExpression Id. */
-  int IRIref = 75;
+  int IRIref = 80;
   /** RegularExpression Id. */
-  int PNAME_NS = 76;
+  int PNAME_NS = 81;
   /** RegularExpression Id. */
-  int PNAME_LN = 77;
+  int PNAME_LN = 82;
   /** RegularExpression Id. */
-  int ATPNAME_NS = 78;
+  int ATPNAME_NS = 83;
   /** RegularExpression Id. */
-  int ATPNAME_LN = 79;
+  int ATPNAME_LN = 84;
   /** RegularExpression Id. */
-  int QUOTE_3D = 80;
+  int QUOTE_3D = 85;
   /** RegularExpression Id. */
-  int QUOTE_3S = 81;
+  int QUOTE_3S = 86;
   /** RegularExpression Id. */
-  int ECHAR = 82;
+  int ECHAR = 87;
   /** RegularExpression Id. */
-  int STRING_LITERAL1 = 83;
+  int STRING_LITERAL1 = 88;
   /** RegularExpression Id. */
-  int STRING_LITERAL2 = 84;
+  int STRING_LITERAL2 = 89;
   /** RegularExpression Id. */
-  int STRING_LITERAL_LONG1 = 85;
+  int STRING_LITERAL_LONG1 = 90;
   /** RegularExpression Id. */
-  int STRING_LITERAL_LONG2 = 86;
+  int STRING_LITERAL_LONG2 = 91;
   /** RegularExpression Id. */
-  int DIGITS = 87;
+  int DIGITS = 92;
   /** RegularExpression Id. */
-  int INTEGER = 88;
+  int INTEGER = 93;
   /** RegularExpression Id. */
-  int DECIMAL = 89;
+  int DECIMAL = 94;
   /** RegularExpression Id. */
-  int DOUBLE = 90;
+  int DOUBLE = 95;
   /** RegularExpression Id. */
-  int EXPONENT = 91;
+  int EXPONENT = 96;
   /** RegularExpression Id. */
-  int LANGTAG = 92;
+  int LANGTAG = 97;
   /** RegularExpression Id. */
-  int A2Z = 93;
+  int A2Z = 98;
   /** RegularExpression Id. */
-  int A2ZN = 94;
+  int A2ZN = 99;
   /** RegularExpression Id. */
-  int PN_CHARS_BASE = 95;
+  int PN_CHARS_BASE = 100;
   /** RegularExpression Id. */
-  int PN_CHARS_U = 96;
+  int PN_CHARS_U = 101;
   /** RegularExpression Id. */
-  int PN_CHARS = 97;
+  int PN_CHARS = 102;
   /** RegularExpression Id. */
-  int PN_PREFIX = 98;
+  int PN_PREFIX = 103;
   /** RegularExpression Id. */
-  int PN_LOCAL = 99;
+  int PN_LOCAL = 104;
   /** RegularExpression Id. */
-  int VARNAME = 100;
+  int VARNAME = 105;
   /** RegularExpression Id. */
-  int PN_LOCAL_ESC = 101;
+  int PN_LOCAL_ESC = 106;
   /** RegularExpression Id. */
-  int PLX = 102;
+  int PLX = 107;
   /** RegularExpression Id. */
-  int PERCENT = 103;
+  int PERCENT = 108;
   /** RegularExpression Id. */
-  int UNKNOWN = 104;
+  int UNKNOWN = 109;
 
   /** Lexical state. */
   int DEFAULT = 0;
@@ -191,6 +191,11 @@
     "\"qualifiedMinCount\"",
     "\"qualifiedMaxCount\"",
     "\"qualifiedValueShapesDisjoint\"",
+    "\"group\"",
+    "\"order\"",
+    "\"name\"",
+    "\"description\"",
+    "\"defaultValue\"",
     "\"^^\"",
     "\" \"",
     "\"\\t\"",
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJTokenManager.java b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJTokenManager.java
index 4b33b42..b524135 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJTokenManager.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/reader/parser/ShaclCompactParserJJTokenManager.java
@@ -42,117 +42,121 @@
    switch(curChar)
    {
       case 9:
-         jjmatchedKind = 43;
+         jjmatchedKind = 48;
          return jjMoveNfa_0(0, 0);
       case 10:
-         jjmatchedKind = 44;
+         jjmatchedKind = 49;
          return jjMoveNfa_0(0, 0);
       case 12:
-         jjmatchedKind = 46;
+         jjmatchedKind = 51;
          return jjMoveNfa_0(0, 0);
       case 13:
-         jjmatchedKind = 45;
+         jjmatchedKind = 50;
          return jjMoveNfa_0(0, 0);
       case 32:
-         jjmatchedKind = 42;
+         jjmatchedKind = 47;
          return jjMoveNfa_0(0, 0);
       case 33:
-         jjmatchedKind = 63;
-         return jjMoveNfa_0(0, 0);
-      case 40:
          jjmatchedKind = 68;
          return jjMoveNfa_0(0, 0);
+      case 40:
+         jjmatchedKind = 73;
+         return jjMoveNfa_0(0, 0);
       case 41:
-         jjmatchedKind = 69;
+         jjmatchedKind = 74;
          return jjMoveNfa_0(0, 0);
       case 42:
-         jjmatchedKind = 66;
+         jjmatchedKind = 71;
          return jjMoveNfa_0(0, 0);
       case 43:
-         jjmatchedKind = 57;
+         jjmatchedKind = 62;
          return jjMoveNfa_0(0, 0);
       case 45:
-         jjmatchedKind = 58;
+         jjmatchedKind = 63;
          return jjMoveStringLiteralDfa1_0(0x2L);
       case 46:
-         jjmatchedKind = 62;
+         jjmatchedKind = 67;
          return jjMoveStringLiteralDfa1_0(0x4L);
       case 47:
-         jjmatchedKind = 65;
+         jjmatchedKind = 70;
          return jjMoveNfa_0(0, 0);
       case 61:
-         jjmatchedKind = 67;
+         jjmatchedKind = 72;
          return jjMoveNfa_0(0, 0);
       case 63:
-         jjmatchedKind = 64;
+         jjmatchedKind = 69;
          return jjMoveNfa_0(0, 0);
       case 64:
-         jjmatchedKind = 60;
+         jjmatchedKind = 65;
          return jjMoveNfa_0(0, 0);
       case 66:
-         return jjMoveStringLiteralDfa1_0(0x20000000000c8L);
+         return jjMoveStringLiteralDfa1_0(0x400000000000c8L);
       case 70:
-         return jjMoveStringLiteralDfa1_0(0x80000000000000L);
+         return jjMoveStringLiteralDfa1_0(0x1000000000000000L);
       case 73:
-         return jjMoveStringLiteralDfa1_0(0x4000000000110L);
+         return jjMoveStringLiteralDfa1_0(0x80000000000110L);
       case 76:
          return jjMoveStringLiteralDfa1_0(0x20L);
       case 80:
-         return jjMoveStringLiteralDfa1_0(0x8000000000000L);
+         return jjMoveStringLiteralDfa1_0(0x100000000000000L);
       case 83:
-         return jjMoveStringLiteralDfa1_0(0x30000000000000L);
+         return jjMoveStringLiteralDfa1_0(0x600000000000000L);
       case 84:
-         return jjMoveStringLiteralDfa1_0(0x40000000000000L);
+         return jjMoveStringLiteralDfa1_0(0x800000000000000L);
       case 91:
-         jjmatchedKind = 72;
+         jjmatchedKind = 77;
          return jjMoveNfa_0(0, 0);
       case 93:
-         jjmatchedKind = 73;
+         jjmatchedKind = 78;
          return jjMoveNfa_0(0, 0);
       case 94:
-         jjmatchedKind = 61;
-         return jjMoveStringLiteralDfa1_0(0x20000000000L);
+         jjmatchedKind = 66;
+         return jjMoveStringLiteralDfa1_0(0x400000000000L);
       case 98:
-         return jjMoveStringLiteralDfa1_0(0x2000000000000L);
+         return jjMoveStringLiteralDfa1_0(0x40000000000000L);
       case 99:
          return jjMoveStringLiteralDfa1_0(0x40010000L);
       case 100:
-         return jjMoveStringLiteralDfa1_0(0x20022000L);
+         return jjMoveStringLiteralDfa1_0(0x300020022000L);
       case 101:
          return jjMoveStringLiteralDfa1_0(0x10000000L);
       case 102:
-         return jjMoveStringLiteralDfa1_0(0x80000004000000L);
+         return jjMoveStringLiteralDfa1_0(0x1000000004000000L);
+      case 103:
+         return jjMoveStringLiteralDfa1_0(0x20000000000L);
       case 104:
          return jjMoveStringLiteralDfa1_0(0x100000000L);
       case 105:
-         return jjMoveStringLiteralDfa1_0(0x4000280000000L);
+         return jjMoveStringLiteralDfa1_0(0x80000280000000L);
       case 108:
          return jjMoveStringLiteralDfa1_0(0x1808000000L);
       case 109:
          return jjMoveStringLiteralDfa1_0(0x1f88000L);
       case 110:
-         return jjMoveStringLiteralDfa1_0(0x40000L);
+         return jjMoveStringLiteralDfa1_0(0x80000040000L);
+      case 111:
+         return jjMoveStringLiteralDfa1_0(0x40000000000L);
       case 112:
-         return jjMoveStringLiteralDfa1_0(0x8000002000000L);
+         return jjMoveStringLiteralDfa1_0(0x100000002000000L);
       case 113:
          return jjMoveStringLiteralDfa1_0(0x1e000000000L);
       case 115:
-         return jjMoveStringLiteralDfa1_0(0x30000000004000L);
+         return jjMoveStringLiteralDfa1_0(0x600000000004000L);
       case 116:
-         return jjMoveStringLiteralDfa1_0(0x40000000001e00L);
+         return jjMoveStringLiteralDfa1_0(0x800000000001e00L);
       case 117:
          return jjMoveStringLiteralDfa1_0(0x400000000L);
       case 123:
-         jjmatchedKind = 70;
+         jjmatchedKind = 75;
          return jjMoveNfa_0(0, 0);
       case 124:
-         jjmatchedKind = 59;
+         jjmatchedKind = 64;
          return jjMoveNfa_0(0, 0);
       case 125:
-         jjmatchedKind = 71;
+         jjmatchedKind = 76;
          return jjMoveNfa_0(0, 0);
       case 65279:
-         jjmatchedKind = 48;
+         jjmatchedKind = 53;
          return jjMoveNfa_0(0, 0);
       default :
          return jjMoveNfa_0(0, 0);
@@ -180,34 +184,34 @@
          }
          break;
       case 65:
-         return jjMoveStringLiteralDfa2_0(active0, 0x82000000000000L);
+         return jjMoveStringLiteralDfa2_0(active0, 0x1040000000000000L);
       case 72:
-         return jjMoveStringLiteralDfa2_0(active0, 0x30000000000000L);
+         return jjMoveStringLiteralDfa2_0(active0, 0x600000000000000L);
       case 77:
-         return jjMoveStringLiteralDfa2_0(active0, 0x4000000000000L);
+         return jjMoveStringLiteralDfa2_0(active0, 0x80000000000000L);
       case 82:
-         return jjMoveStringLiteralDfa2_0(active0, 0x48000000000110L);
+         return jjMoveStringLiteralDfa2_0(active0, 0x900000000000110L);
       case 94:
-         if ((active0 & 0x20000000000L) != 0L)
+         if ((active0 & 0x400000000000L) != 0L)
          {
-            jjmatchedKind = 41;
+            jjmatchedKind = 46;
             jjmatchedPos = 1;
          }
          break;
       case 97:
-         return jjMoveStringLiteralDfa2_0(active0, 0x8200010b621e00L);
+         return jjMoveStringLiteralDfa2_0(active0, 0x104008010b621e00L);
       case 101:
-         return jjMoveStringLiteralDfa2_0(active0, 0x180000e000L);
+         return jjMoveStringLiteralDfa2_0(active0, 0x30180000e000L);
       case 103:
          return jjMoveStringLiteralDfa2_0(active0, 0x80000000L);
       case 104:
-         return jjMoveStringLiteralDfa2_0(active0, 0x30000000000000L);
+         return jjMoveStringLiteralDfa2_0(active0, 0x600000000000000L);
       case 105:
          return jjMoveStringLiteralDfa2_0(active0, 0x20980020L);
       case 108:
          return jjMoveStringLiteralDfa2_0(active0, 0x440100c8L);
       case 109:
-         return jjMoveStringLiteralDfa2_0(active0, 0x4000000000000L);
+         return jjMoveStringLiteralDfa2_0(active0, 0x80000000000000L);
       case 110:
          if ((active0 & 0x200000000L) != 0L)
          {
@@ -220,7 +224,7 @@
       case 113:
          return jjMoveStringLiteralDfa2_0(active0, 0x10000000L);
       case 114:
-         return jjMoveStringLiteralDfa2_0(active0, 0x48000000000000L);
+         return jjMoveStringLiteralDfa2_0(active0, 0x900060000000000L);
       case 117:
          return jjMoveStringLiteralDfa2_0(active0, 0x1e000000000L);
       default :
@@ -238,9 +242,9 @@
    switch(curChar)
    {
       case 65:
-         return jjMoveStringLiteralDfa3_0(active0, 0x30000000000000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x600000000000000L);
       case 69:
-         return jjMoveStringLiteralDfa3_0(active0, 0x8000000000000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x100000000000000L);
       case 73:
          if ((active0 & 0x10L) != 0L)
          {
@@ -249,37 +253,41 @@
          }
          return jjMoveStringLiteralDfa3_0(active0, 0x100L);
       case 76:
-         return jjMoveStringLiteralDfa3_0(active0, 0x80000000000000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x1000000000000000L);
       case 80:
-         return jjMoveStringLiteralDfa3_0(active0, 0x4000000000000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x80000000000000L);
       case 83:
-         return jjMoveStringLiteralDfa3_0(active0, 0x2000000000000L);
-      case 85:
          return jjMoveStringLiteralDfa3_0(active0, 0x40000000000000L);
+      case 85:
+         return jjMoveStringLiteralDfa3_0(active0, 0x800000000000000L);
       case 97:
-         return jjMoveStringLiteralDfa3_0(active0, 0x3001e0040120c8L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x60001e0040120c8L);
       case 100:
-         return jjMoveStringLiteralDfa3_0(active0, 0x40000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x40000040000L);
       case 101:
-         return jjMoveStringLiteralDfa3_0(active0, 0x8000000000000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x100000000000000L);
+      case 102:
+         return jjMoveStringLiteralDfa3_0(active0, 0x200000000000L);
       case 105:
          return jjMoveStringLiteralDfa3_0(active0, 0x400000000L);
       case 108:
-         return jjMoveStringLiteralDfa3_0(active0, 0x80000000000000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x1000000000000000L);
+      case 109:
+         return jjMoveStringLiteralDfa3_0(active0, 0x80000000000L);
       case 110:
          return jjMoveStringLiteralDfa3_0(active0, 0x88980000L);
       case 111:
-         return jjMoveStringLiteralDfa3_0(active0, 0x40000000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x20040000000L);
       case 112:
-         return jjMoveStringLiteralDfa3_0(active0, 0x4000000000000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x80000000000000L);
       case 114:
          return jjMoveStringLiteralDfa3_0(active0, 0x1e00L);
       case 115:
-         return jjMoveStringLiteralDfa3_0(active0, 0x2001920008000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x40101920008000L);
       case 116:
          return jjMoveStringLiteralDfa3_0(active0, 0x2020020L);
       case 117:
-         return jjMoveStringLiteralDfa3_0(active0, 0x40000010000000L);
+         return jjMoveStringLiteralDfa3_0(active0, 0x800000010000000L);
       case 118:
          return jjMoveStringLiteralDfa3_0(active0, 0x4000L);
       case 120:
@@ -299,39 +307,39 @@
    switch(curChar)
    {
       case 69:
-         if ((active0 & 0x2000000000000L) != 0L)
-         {
-            jjmatchedKind = 49;
-            jjmatchedPos = 3;
-         }
-         else if ((active0 & 0x40000000000000L) != 0L)
+         if ((active0 & 0x40000000000000L) != 0L)
          {
             jjmatchedKind = 54;
             jjmatchedPos = 3;
          }
+         else if ((active0 & 0x800000000000000L) != 0L)
+         {
+            jjmatchedKind = 59;
+            jjmatchedPos = 3;
+         }
          return jjMoveStringLiteralDfa4_0(active0, 0x280000L);
       case 70:
-         return jjMoveStringLiteralDfa4_0(active0, 0x8000000000000L);
+         return jjMoveStringLiteralDfa4_0(active0, 0x100000000000000L);
       case 73:
          return jjMoveStringLiteralDfa4_0(active0, 0x500000L);
       case 76:
          return jjMoveStringLiteralDfa4_0(active0, 0x1800000L);
       case 79:
-         return jjMoveStringLiteralDfa4_0(active0, 0x4000000000100L);
+         return jjMoveStringLiteralDfa4_0(active0, 0x80000000000100L);
       case 80:
-         return jjMoveStringLiteralDfa4_0(active0, 0x30000000000000L);
+         return jjMoveStringLiteralDfa4_0(active0, 0x600000000000000L);
       case 83:
-         return jjMoveStringLiteralDfa4_0(active0, 0x80000000000000L);
+         return jjMoveStringLiteralDfa4_0(active0, 0x1000000000000000L);
       case 86:
          return jjMoveStringLiteralDfa4_0(active0, 0x100000000L);
       case 97:
-         return jjMoveStringLiteralDfa4_0(active0, 0x10020000L);
+         return jjMoveStringLiteralDfa4_0(active0, 0x200010020000L);
       case 99:
-         return jjMoveStringLiteralDfa4_0(active0, 0x2000L);
+         return jjMoveStringLiteralDfa4_0(active0, 0x100000002000L);
       case 101:
-         if ((active0 & 0x2000000000000L) != 0L)
+         if ((active0 & 0x80000000000L) != 0L)
          {
-            jjmatchedKind = 49;
+            jjmatchedKind = 43;
             jjmatchedPos = 3;
          }
          else if ((active0 & 0x40000000000000L) != 0L)
@@ -339,9 +347,14 @@
             jjmatchedKind = 54;
             jjmatchedPos = 3;
          }
-         return jjMoveStringLiteralDfa4_0(active0, 0x44020L);
+         else if ((active0 & 0x800000000000000L) != 0L)
+         {
+            jjmatchedKind = 59;
+            jjmatchedPos = 3;
+         }
+         return jjMoveStringLiteralDfa4_0(active0, 0x40000044020L);
       case 102:
-         return jjMoveStringLiteralDfa4_0(active0, 0x8000000000000L);
+         return jjMoveStringLiteralDfa4_0(active0, 0x100000000000000L);
       case 103:
          return jjMoveStringLiteralDfa4_0(active0, 0xc001e00L);
       case 106:
@@ -351,15 +364,17 @@
       case 110:
          return jjMoveStringLiteralDfa4_0(active0, 0xc8L);
       case 111:
-         return jjMoveStringLiteralDfa4_0(active0, 0x4000080000000L);
+         return jjMoveStringLiteralDfa4_0(active0, 0x80000080000000L);
       case 112:
-         return jjMoveStringLiteralDfa4_0(active0, 0x30000000000000L);
+         return jjMoveStringLiteralDfa4_0(active0, 0x600000000000000L);
       case 113:
          return jjMoveStringLiteralDfa4_0(active0, 0x400000000L);
       case 115:
-         return jjMoveStringLiteralDfa4_0(active0, 0x80001840018000L);
+         return jjMoveStringLiteralDfa4_0(active0, 0x1000001840018000L);
       case 116:
          return jjMoveStringLiteralDfa4_0(active0, 0x2000000L);
+      case 117:
+         return jjMoveStringLiteralDfa4_0(active0, 0x20000000000L);
       default :
          break;
    }
@@ -375,41 +390,41 @@
    switch(curChar)
    {
       case 69:
-         if ((active0 & 0x20000000000000L) != 0L)
+         if ((active0 & 0x400000000000000L) != 0L)
          {
-            jjmatchedKind = 53;
+            jjmatchedKind = 58;
             jjmatchedPos = 4;
          }
-         else if ((active0 & 0x80000000000000L) != 0L)
+         else if ((active0 & 0x1000000000000000L) != 0L)
          {
-            jjmatchedKind = 55;
+            jjmatchedKind = 60;
             jjmatchedPos = 4;
          }
-         return jjMoveStringLiteralDfa5_0(active0, 0x10000000000000L);
+         return jjMoveStringLiteralDfa5_0(active0, 0x200000000000000L);
       case 73:
-         return jjMoveStringLiteralDfa5_0(active0, 0x8000000000000L);
+         return jjMoveStringLiteralDfa5_0(active0, 0x100000000000000L);
       case 75:
          return jjMoveStringLiteralDfa5_0(active0, 0x40000L);
       case 82:
-         return jjMoveStringLiteralDfa5_0(active0, 0x4000000000000L);
+         return jjMoveStringLiteralDfa5_0(active0, 0x80000000000000L);
       case 84:
          return jjMoveStringLiteralDfa5_0(active0, 0x1800000000L);
       case 97:
          return jjMoveStringLiteralDfa5_0(active0, 0x100008000L);
       case 101:
-         if ((active0 & 0x20000000000000L) != 0L)
+         if ((active0 & 0x400000000000000L) != 0L)
          {
-            jjmatchedKind = 53;
+            jjmatchedKind = 58;
             jjmatchedPos = 4;
          }
-         else if ((active0 & 0x80000000000000L) != 0L)
+         else if ((active0 & 0x1000000000000000L) != 0L)
          {
-            jjmatchedKind = 55;
+            jjmatchedKind = 60;
             jjmatchedPos = 4;
          }
-         return jjMoveStringLiteralDfa5_0(active0, 0x10000043801e00L);
+         return jjMoveStringLiteralDfa5_0(active0, 0x200000043801e00L);
       case 105:
-         return jjMoveStringLiteralDfa5_0(active0, 0x801e000000000L);
+         return jjMoveStringLiteralDfa5_0(active0, 0x10001e000000000L);
       case 107:
          return jjMoveStringLiteralDfa5_0(active0, 0xc8L);
       case 108:
@@ -418,8 +433,20 @@
          return jjMoveStringLiteralDfa5_0(active0, 0x500000L);
       case 111:
          return jjMoveStringLiteralDfa5_0(active0, 0x20000000L);
+      case 112:
+         if ((active0 & 0x20000000000L) != 0L)
+         {
+            jjmatchedKind = 41;
+            jjmatchedPos = 4;
+         }
+         break;
       case 114:
-         return jjMoveStringLiteralDfa5_0(active0, 0x4000080004120L);
+         if ((active0 & 0x40000000000L) != 0L)
+         {
+            jjmatchedKind = 42;
+            jjmatchedPos = 4;
+         }
+         return jjMoveStringLiteralDfa5_0(active0, 0x80100080004120L);
       case 115:
          if ((active0 & 0x10000L) != 0L)
          {
@@ -435,7 +462,7 @@
       case 116:
          return jjMoveStringLiteralDfa5_0(active0, 0x22000L);
       case 117:
-         return jjMoveStringLiteralDfa5_0(active0, 0x408000000L);
+         return jjMoveStringLiteralDfa5_0(active0, 0x200408000000L);
       case 120:
          return jjMoveStringLiteralDfa5_0(active0, 0x280000L);
       default :
@@ -453,24 +480,24 @@
    switch(curChar)
    {
       case 67:
-         return jjMoveStringLiteralDfa6_0(active0, 0x10000000000000L);
+         return jjMoveStringLiteralDfa6_0(active0, 0x200000000000000L);
       case 76:
          return jjMoveStringLiteralDfa6_0(active0, 0x100L);
       case 78:
          return jjMoveStringLiteralDfa6_0(active0, 0xc8L);
       case 84:
-         return jjMoveStringLiteralDfa6_0(active0, 0x4000000000000L);
+         return jjMoveStringLiteralDfa6_0(active0, 0x80000000000000L);
       case 88:
-         if ((active0 & 0x8000000000000L) != 0L)
+         if ((active0 & 0x100000000000000L) != 0L)
          {
-            jjmatchedKind = 51;
+            jjmatchedKind = 56;
             jjmatchedPos = 5;
          }
          break;
       case 97:
          return jjMoveStringLiteralDfa6_0(active0, 0x8000020L);
       case 99:
-         return jjMoveStringLiteralDfa6_0(active0, 0x10000000780000L);
+         return jjMoveStringLiteralDfa6_0(active0, 0x200000000780000L);
       case 100:
          if ((active0 & 0x40000000L) != 0L)
          {
@@ -487,9 +514,9 @@
       case 104:
          return jjMoveStringLiteralDfa6_0(active0, 0x1800000000L);
       case 105:
-         return jjMoveStringLiteralDfa6_0(active0, 0x20046000L);
+         return jjMoveStringLiteralDfa6_0(active0, 0x100020046000L);
       case 108:
-         return jjMoveStringLiteralDfa6_0(active0, 0x100000000L);
+         return jjMoveStringLiteralDfa6_0(active0, 0x200100000000L);
       case 110:
          return jjMoveStringLiteralDfa6_0(active0, 0x1800000L);
       case 114:
@@ -502,11 +529,11 @@
          }
          break;
       case 116:
-         return jjMoveStringLiteralDfa6_0(active0, 0x4000000001e00L);
+         return jjMoveStringLiteralDfa6_0(active0, 0x80000000001e00L);
       case 120:
-         if ((active0 & 0x8000000000000L) != 0L)
+         if ((active0 & 0x100000000000000L) != 0L)
          {
-            jjmatchedKind = 51;
+            jjmatchedKind = 56;
             jjmatchedPos = 5;
          }
          break;
@@ -529,15 +556,15 @@
       case 67:
          return jjMoveStringLiteralDfa7_0(active0, 0x1000L);
       case 76:
-         return jjMoveStringLiteralDfa7_0(active0, 0x10000400000000L);
+         return jjMoveStringLiteralDfa7_0(active0, 0x200000400000000L);
       case 78:
          return jjMoveStringLiteralDfa7_0(active0, 0x200L);
       case 79:
          return jjMoveStringLiteralDfa7_0(active0, 0x400L);
       case 83:
-         if ((active0 & 0x4000000000000L) != 0L)
+         if ((active0 & 0x80000000000000L) != 0L)
          {
-            jjmatchedKind = 50;
+            jjmatchedKind = 55;
             jjmatchedPos = 6;
          }
          return jjMoveStringLiteralDfa7_0(active0, 0x800L);
@@ -562,7 +589,7 @@
             jjmatchedKind = 5;
             jjmatchedPos = 6;
          }
-         return jjMoveStringLiteralDfa7_0(active0, 0x10000000780000L);
+         return jjMoveStringLiteralDfa7_0(active0, 0x200000000780000L);
       case 110:
          if ((active0 & 0x2000000L) != 0L)
          {
@@ -573,16 +600,16 @@
       case 111:
          return jjMoveStringLiteralDfa7_0(active0, 0xc8L);
       case 112:
-         return jjMoveStringLiteralDfa7_0(active0, 0x20000L);
+         return jjMoveStringLiteralDfa7_0(active0, 0x100000020000L);
       case 115:
-         if ((active0 & 0x4000000000000L) != 0L)
+         if ((active0 & 0x80000000000000L) != 0L)
          {
-            jjmatchedKind = 50;
+            jjmatchedKind = 55;
             jjmatchedPos = 6;
          }
          break;
       case 116:
-         return jjMoveStringLiteralDfa7_0(active0, 0x4000L);
+         return jjMoveStringLiteralDfa7_0(active0, 0x200000004000L);
       case 117:
          return jjMoveStringLiteralDfa7_0(active0, 0x100000000L);
       case 118:
@@ -602,11 +629,13 @@
    switch(curChar)
    {
       case 65:
-         return jjMoveStringLiteralDfa8_0(active0, 0x10000000000000L);
+         return jjMoveStringLiteralDfa8_0(active0, 0x200000000000000L);
       case 80:
          return jjMoveStringLiteralDfa8_0(active0, 0x80000000L);
+      case 86:
+         return jjMoveStringLiteralDfa8_0(active0, 0x200000000000L);
       case 97:
-         return jjMoveStringLiteralDfa8_0(active0, 0x10000400002000L);
+         return jjMoveStringLiteralDfa8_0(active0, 0x200000400002000L);
       case 98:
          return jjMoveStringLiteralDfa8_0(active0, 0x400L);
       case 100:
@@ -645,7 +674,7 @@
             jjmatchedKind = 29;
             jjmatchedPos = 7;
          }
-         return jjMoveStringLiteralDfa8_0(active0, 0x1800100L);
+         return jjMoveStringLiteralDfa8_0(active0, 0x100001800100L);
       case 117:
          return jjMoveStringLiteralDfa8_0(active0, 0x780800L);
       case 121:
@@ -674,9 +703,9 @@
       case 79:
          return jjMoveStringLiteralDfa9_0(active0, 0x1000000000L);
       case 83:
-         return jjMoveStringLiteralDfa9_0(active0, 0x10000000000000L);
+         return jjMoveStringLiteralDfa9_0(active0, 0x200000000000000L);
       case 97:
-         return jjMoveStringLiteralDfa9_0(active0, 0x1000L);
+         return jjMoveStringLiteralDfa9_0(active0, 0x200000001000L);
       case 98:
          return jjMoveStringLiteralDfa9_0(active0, 0x800L);
       case 100:
@@ -700,6 +729,8 @@
             jjmatchedPos = 8;
          }
          break;
+      case 105:
+         return jjMoveStringLiteralDfa9_0(active0, 0x100000000000L);
       case 106:
          return jjMoveStringLiteralDfa9_0(active0, 0x400L);
       case 110:
@@ -707,7 +738,7 @@
       case 114:
          return jjMoveStringLiteralDfa9_0(active0, 0x80000000L);
       case 115:
-         return jjMoveStringLiteralDfa9_0(active0, 0x10000000780000L);
+         return jjMoveStringLiteralDfa9_0(active0, 0x200000000780000L);
       case 116:
          return jjMoveStringLiteralDfa9_0(active0, 0x2000L);
       default :
@@ -729,9 +760,9 @@
       case 79:
          return jjMoveStringLiteralDfa10_0(active0, 0xc0L);
       case 83:
-         if ((active0 & 0x10000000000000L) != 0L)
+         if ((active0 & 0x200000000000000L) != 0L)
          {
-            jjmatchedKind = 52;
+            jjmatchedKind = 57;
             jjmatchedPos = 9;
          }
          break;
@@ -755,6 +786,8 @@
          return jjMoveStringLiteralDfa10_0(active0, 0x780000L);
       case 106:
          return jjMoveStringLiteralDfa10_0(active0, 0x800L);
+      case 108:
+         return jjMoveStringLiteralDfa10_0(active0, 0x200000000000L);
       case 110:
          if ((active0 & 0x8000000L) != 0L)
          {
@@ -763,13 +796,13 @@
          }
          break;
       case 111:
-         return jjMoveStringLiteralDfa10_0(active0, 0x80000000L);
+         return jjMoveStringLiteralDfa10_0(active0, 0x100080000000L);
       case 114:
          return jjMoveStringLiteralDfa10_0(active0, 0x1000000100L);
       case 115:
-         if ((active0 & 0x10000000000000L) != 0L)
+         if ((active0 & 0x200000000000000L) != 0L)
          {
-            jjmatchedKind = 52;
+            jjmatchedKind = 57;
             jjmatchedPos = 9;
          }
          return jjMoveStringLiteralDfa10_0(active0, 0x1000L);
@@ -804,6 +837,13 @@
          return jjMoveStringLiteralDfa11_0(active0, 0x800L);
       case 105:
          return jjMoveStringLiteralDfa11_0(active0, 0x4000000000L);
+      case 110:
+         if ((active0 & 0x100000000000L) != 0L)
+         {
+            jjmatchedKind = 44;
+            jjmatchedPos = 10;
+         }
+         break;
       case 112:
          return jjMoveStringLiteralDfa11_0(active0, 0x80000000L);
       case 114:
@@ -815,6 +855,8 @@
             jjmatchedPos = 10;
          }
          break;
+      case 117:
+         return jjMoveStringLiteralDfa11_0(active0, 0x200000000000L);
       case 118:
          return jjMoveStringLiteralDfa11_0(active0, 0x780000L);
       default :
@@ -858,6 +900,11 @@
             jjmatchedKind = 22;
             jjmatchedPos = 11;
          }
+         else if ((active0 & 0x200000000000L) != 0L)
+         {
+            jjmatchedKind = 45;
+            jjmatchedPos = 11;
+         }
          return jjMoveStringLiteralDfa12_0(active0, 0x80000000L);
       case 108:
          if ((active0 & 0x100L) != 0L)
@@ -1301,8 +1348,8 @@
                case 0:
                   if ((0x3ff000000000000L & l) != 0L)
                   {
-                     if (kind > 88)
-                        kind = 88;
+                     if (kind > 93)
+                        kind = 93;
                      { jjCheckNAddStates(0, 6); }
                   }
                   else if ((0x280000000000L & l) != 0L)
@@ -1311,8 +1358,8 @@
                      { jjCheckNAddTwoStates(164, 166); }
                   else if (curChar == 58)
                   {
-                     if (kind > 76)
-                        kind = 76;
+                     if (kind > 81)
+                        kind = 81;
                      { jjCheckNAddStates(12, 14); }
                   }
                   else if (curChar == 34)
@@ -1323,8 +1370,8 @@
                      { jjCheckNAddStates(15, 17); }
                   else if (curChar == 35)
                   {
-                     if (kind > 47)
-                        kind = 47;
+                     if (kind > 52)
+                        kind = 52;
                      { jjCheckNAddStates(18, 20); }
                   }
                   if (curChar == 34)
@@ -1335,17 +1382,17 @@
                case 1:
                   if ((0xffffffffffffdbffL & l) == 0L)
                      break;
-                  if (kind > 47)
-                     kind = 47;
+                  if (kind > 52)
+                     kind = 52;
                   { jjCheckNAddStates(18, 20); }
                   break;
                case 2:
-                  if ((0x2400L & l) != 0L && kind > 47)
-                     kind = 47;
+                  if ((0x2400L & l) != 0L && kind > 52)
+                     kind = 52;
                   break;
                case 3:
-                  if (curChar == 10 && kind > 47)
-                     kind = 47;
+                  if (curChar == 10 && kind > 52)
+                     kind = 52;
                   break;
                case 4:
                   if (curChar == 13)
@@ -1377,8 +1424,8 @@
                      { jjCheckNAddStates(15, 17); }
                   break;
                case 13:
-                  if (curChar == 62 && kind > 75)
-                     kind = 75;
+                  if (curChar == 62 && kind > 80)
+                     kind = 80;
                   break;
                case 15:
                   if ((0x3ff000000000000L & l) != 0L)
@@ -1405,8 +1452,8 @@
                      jjstateSet[jjnewStateCnt++] = 21;
                   break;
                case 23:
-                  if ((0x8400000000L & l) != 0L && kind > 82)
-                     kind = 82;
+                  if ((0x8400000000L & l) != 0L && kind > 87)
+                     kind = 87;
                   break;
                case 24:
                   if (curChar == 39)
@@ -1417,8 +1464,8 @@
                      { jjCheckNAddStates(24, 26); }
                   break;
                case 26:
-                  if (curChar == 39 && kind > 83)
-                     kind = 83;
+                  if (curChar == 39 && kind > 88)
+                     kind = 88;
                   break;
                case 28:
                   if ((0x8400000000L & l) != 0L)
@@ -1474,8 +1521,8 @@
                      { jjCheckNAddStates(21, 23); }
                   break;
                case 44:
-                  if (curChar == 34 && kind > 84)
-                     kind = 84;
+                  if (curChar == 34 && kind > 89)
+                     kind = 89;
                   break;
                case 46:
                   if ((0x8400000000L & l) != 0L)
@@ -1585,8 +1632,8 @@
                      jjstateSet[jjnewStateCnt++] = 79;
                   break;
                case 80:
-                  if (curChar == 39 && kind > 85)
-                     kind = 85;
+                  if (curChar == 39 && kind > 90)
+                     kind = 90;
                   break;
                case 81:
                   if (curChar == 39)
@@ -1663,8 +1710,8 @@
                      jjstateSet[jjnewStateCnt++] = 103;
                   break;
                case 104:
-                  if (curChar == 34 && kind > 86)
-                     kind = 86;
+                  if (curChar == 34 && kind > 91)
+                     kind = 91;
                   break;
                case 105:
                   if (curChar == 34)
@@ -1687,8 +1734,8 @@
                      jjstateSet[jjnewStateCnt++] = 111;
                   break;
                case 111:
-                  if (curChar == 58 && kind > 76)
-                     kind = 76;
+                  if (curChar == 58 && kind > 81)
+                     kind = 81;
                   break;
                case 112:
                   if ((0x3ff600000000000L & l) != 0L)
@@ -1705,8 +1752,8 @@
                case 115:
                   if ((0x7ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 77)
-                     kind = 77;
+                  if (kind > 82)
+                     kind = 82;
                   { jjCheckNAddStates(43, 46); }
                   break;
                case 116:
@@ -1714,8 +1761,8 @@
                      { jjCheckNAddStates(43, 46); }
                   break;
                case 117:
-                  if ((0x7ff200000000000L & l) != 0L && kind > 77)
-                     kind = 77;
+                  if ((0x7ff200000000000L & l) != 0L && kind > 82)
+                     kind = 82;
                   break;
                case 119:
                   if ((0xa800fffa00000000L & l) != 0L)
@@ -1738,18 +1785,18 @@
                      jjstateSet[jjnewStateCnt++] = 124;
                   break;
                case 124:
-                  if ((0x3ff000000000000L & l) != 0L && kind > 77)
-                     kind = 77;
+                  if ((0x3ff000000000000L & l) != 0L && kind > 82)
+                     kind = 82;
                   break;
                case 125:
-                  if ((0xa800fffa00000000L & l) != 0L && kind > 77)
-                     kind = 77;
+                  if ((0xa800fffa00000000L & l) != 0L && kind > 82)
+                     kind = 82;
                   break;
                case 127:
                   if ((0xa800fffa00000000L & l) == 0L)
                      break;
-                  if (kind > 77)
-                     kind = 77;
+                  if (kind > 82)
+                     kind = 82;
                   { jjCheckNAddStates(43, 46); }
                   break;
                case 128:
@@ -1763,15 +1810,15 @@
                case 130:
                   if ((0x3ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 77)
-                     kind = 77;
+                  if (kind > 82)
+                     kind = 82;
                   { jjCheckNAddStates(43, 46); }
                   break;
                case 131:
                   if (curChar != 58)
                      break;
-                  if (kind > 76)
-                     kind = 76;
+                  if (kind > 81)
+                     kind = 81;
                   { jjCheckNAddStates(12, 14); }
                   break;
                case 134:
@@ -1783,8 +1830,8 @@
                      jjstateSet[jjnewStateCnt++] = 136;
                   break;
                case 136:
-                  if (curChar == 58 && kind > 78)
-                     kind = 78;
+                  if (curChar == 58 && kind > 83)
+                     kind = 83;
                   break;
                case 138:
                   if ((0x3ff600000000000L & l) != 0L)
@@ -1801,8 +1848,8 @@
                case 141:
                   if ((0x7ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 79)
-                     kind = 79;
+                  if (kind > 84)
+                     kind = 84;
                   { jjCheckNAddStates(56, 59); }
                   break;
                case 142:
@@ -1810,8 +1857,8 @@
                      { jjCheckNAddStates(56, 59); }
                   break;
                case 143:
-                  if ((0x7ff200000000000L & l) != 0L && kind > 79)
-                     kind = 79;
+                  if ((0x7ff200000000000L & l) != 0L && kind > 84)
+                     kind = 84;
                   break;
                case 145:
                   if ((0xa800fffa00000000L & l) != 0L)
@@ -1834,18 +1881,18 @@
                      jjstateSet[jjnewStateCnt++] = 150;
                   break;
                case 150:
-                  if ((0x3ff000000000000L & l) != 0L && kind > 79)
-                     kind = 79;
+                  if ((0x3ff000000000000L & l) != 0L && kind > 84)
+                     kind = 84;
                   break;
                case 151:
-                  if ((0xa800fffa00000000L & l) != 0L && kind > 79)
-                     kind = 79;
+                  if ((0xa800fffa00000000L & l) != 0L && kind > 84)
+                     kind = 84;
                   break;
                case 153:
                   if ((0xa800fffa00000000L & l) == 0L)
                      break;
-                  if (kind > 79)
-                     kind = 79;
+                  if (kind > 84)
+                     kind = 84;
                   { jjCheckNAddStates(56, 59); }
                   break;
                case 154:
@@ -1859,8 +1906,8 @@
                case 156:
                   if ((0x3ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 79)
-                     kind = 79;
+                  if (kind > 84)
+                     kind = 84;
                   { jjCheckNAddStates(56, 59); }
                   break;
                case 158:
@@ -1870,8 +1917,8 @@
                case 159:
                   if ((0x3ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 92)
-                     kind = 92;
+                  if (kind > 97)
+                     kind = 97;
                   { jjCheckNAddTwoStates(158, 159); }
                   break;
                case 160:
@@ -1881,8 +1928,8 @@
                case 161:
                   if ((0x3ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 88)
-                     kind = 88;
+                  if (kind > 93)
+                     kind = 93;
                   { jjCheckNAdd(161); }
                   break;
                case 162:
@@ -1896,8 +1943,8 @@
                case 164:
                   if ((0x3ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 89)
-                     kind = 89;
+                  if (kind > 94)
+                     kind = 94;
                   { jjCheckNAdd(164); }
                   break;
                case 165:
@@ -1915,8 +1962,8 @@
                case 169:
                   if ((0x3ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 90)
-                     kind = 90;
+                  if (kind > 95)
+                     kind = 95;
                   { jjCheckNAdd(169); }
                   break;
                case 170:
@@ -1942,8 +1989,8 @@
                case 176:
                   if ((0x3ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 90)
-                     kind = 90;
+                  if (kind > 95)
+                     kind = 95;
                   { jjCheckNAdd(176); }
                   break;
                case 177:
@@ -1957,15 +2004,15 @@
                case 180:
                   if ((0x3ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 90)
-                     kind = 90;
+                  if (kind > 95)
+                     kind = 95;
                   { jjCheckNAdd(180); }
                   break;
                case 181:
                   if ((0x3ff000000000000L & l) == 0L)
                      break;
-                  if (kind > 88)
-                     kind = 88;
+                  if (kind > 93)
+                     kind = 93;
                   { jjCheckNAddStates(0, 6); }
                   break;
                case 182:
@@ -1992,8 +2039,8 @@
                      jjstateSet[jjnewStateCnt++] = 23;
                   break;
                case 1:
-                  if (kind > 47)
-                     kind = 47;
+                  if (kind > 52)
+                     kind = 52;
                   { jjAddStates(18, 20); }
                   break;
                case 6:
@@ -2058,8 +2105,8 @@
                      jjstateSet[jjnewStateCnt++] = 23;
                   break;
                case 23:
-                  if ((0x14404410000000L & l) != 0L && kind > 82)
-                     kind = 82;
+                  if ((0x14404410000000L & l) != 0L && kind > 87)
+                     kind = 87;
                   break;
                case 25:
                   if ((0xffffffffefffffffL & l) != 0L)
@@ -2328,8 +2375,8 @@
                case 115:
                   if ((0x7fffffe87fffffeL & l) == 0L)
                      break;
-                  if (kind > 77)
-                     kind = 77;
+                  if (kind > 82)
+                     kind = 82;
                   { jjCheckNAddStates(43, 46); }
                   break;
                case 116:
@@ -2337,8 +2384,8 @@
                      { jjCheckNAddStates(43, 46); }
                   break;
                case 117:
-                  if ((0x7fffffe87fffffeL & l) != 0L && kind > 77)
-                     kind = 77;
+                  if ((0x7fffffe87fffffeL & l) != 0L && kind > 82)
+                     kind = 82;
                   break;
                case 118:
                   if (curChar == 92)
@@ -2361,12 +2408,12 @@
                      jjstateSet[jjnewStateCnt++] = 124;
                   break;
                case 124:
-                  if ((0x7e0000007eL & l) != 0L && kind > 77)
-                     kind = 77;
+                  if ((0x7e0000007eL & l) != 0L && kind > 82)
+                     kind = 82;
                   break;
                case 125:
-                  if ((0x4000000080000001L & l) != 0L && kind > 77)
-                     kind = 77;
+                  if ((0x4000000080000001L & l) != 0L && kind > 82)
+                     kind = 82;
                   break;
                case 126:
                   if (curChar == 92)
@@ -2375,8 +2422,8 @@
                case 127:
                   if ((0x4000000080000001L & l) == 0L)
                      break;
-                  if (kind > 77)
-                     kind = 77;
+                  if (kind > 82)
+                     kind = 82;
                   { jjCheckNAddStates(43, 46); }
                   break;
                case 129:
@@ -2386,8 +2433,8 @@
                case 130:
                   if ((0x7e0000007eL & l) == 0L)
                      break;
-                  if (kind > 77)
-                     kind = 77;
+                  if (kind > 82)
+                     kind = 82;
                   { jjCheckNAddStates(43, 46); }
                   break;
                case 132:
@@ -2421,8 +2468,8 @@
                case 141:
                   if ((0x7fffffe87fffffeL & l) == 0L)
                      break;
-                  if (kind > 79)
-                     kind = 79;
+                  if (kind > 84)
+                     kind = 84;
                   { jjCheckNAddStates(56, 59); }
                   break;
                case 142:
@@ -2430,8 +2477,8 @@
                      { jjCheckNAddStates(56, 59); }
                   break;
                case 143:
-                  if ((0x7fffffe87fffffeL & l) != 0L && kind > 79)
-                     kind = 79;
+                  if ((0x7fffffe87fffffeL & l) != 0L && kind > 84)
+                     kind = 84;
                   break;
                case 144:
                   if (curChar == 92)
@@ -2454,12 +2501,12 @@
                      jjstateSet[jjnewStateCnt++] = 150;
                   break;
                case 150:
-                  if ((0x7e0000007eL & l) != 0L && kind > 79)
-                     kind = 79;
+                  if ((0x7e0000007eL & l) != 0L && kind > 84)
+                     kind = 84;
                   break;
                case 151:
-                  if ((0x4000000080000001L & l) != 0L && kind > 79)
-                     kind = 79;
+                  if ((0x4000000080000001L & l) != 0L && kind > 84)
+                     kind = 84;
                   break;
                case 152:
                   if (curChar == 92)
@@ -2468,8 +2515,8 @@
                case 153:
                   if ((0x4000000080000001L & l) == 0L)
                      break;
-                  if (kind > 79)
-                     kind = 79;
+                  if (kind > 84)
+                     kind = 84;
                   { jjCheckNAddStates(56, 59); }
                   break;
                case 155:
@@ -2479,22 +2526,22 @@
                case 156:
                   if ((0x7e0000007eL & l) == 0L)
                      break;
-                  if (kind > 79)
-                     kind = 79;
+                  if (kind > 84)
+                     kind = 84;
                   { jjCheckNAddStates(56, 59); }
                   break;
                case 157:
                   if ((0x7fffffe07fffffeL & l) == 0L)
                      break;
-                  if (kind > 92)
-                     kind = 92;
+                  if (kind > 97)
+                     kind = 97;
                   { jjCheckNAddTwoStates(157, 158); }
                   break;
                case 159:
                   if ((0x7fffffe07fffffeL & l) == 0L)
                      break;
-                  if (kind > 92)
-                     kind = 92;
+                  if (kind > 97)
+                     kind = 97;
                   { jjCheckNAddTwoStates(158, 159); }
                   break;
                case 167:
@@ -2531,8 +2578,8 @@
                case 1:
                   if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
                      break;
-                  if (kind > 47)
-                     kind = 47;
+                  if (kind > 52)
+                     kind = 52;
                   { jjAddStates(18, 20); }
                   break;
                case 6:
@@ -2574,8 +2621,8 @@
                case 115:
                   if (!jjCanMove_1(hiByte, i1, i2, l1, l2))
                      break;
-                  if (kind > 77)
-                     kind = 77;
+                  if (kind > 82)
+                     kind = 82;
                   { jjCheckNAddStates(43, 46); }
                   break;
                case 116:
@@ -2583,8 +2630,8 @@
                      { jjCheckNAddStates(43, 46); }
                   break;
                case 117:
-                  if (jjCanMove_2(hiByte, i1, i2, l1, l2) && kind > 77)
-                     kind = 77;
+                  if (jjCanMove_2(hiByte, i1, i2, l1, l2) && kind > 82)
+                     kind = 82;
                   break;
                case 133:
                   if (jjCanMove_1(hiByte, i1, i2, l1, l2))
@@ -2613,8 +2660,8 @@
                case 141:
                   if (!jjCanMove_1(hiByte, i1, i2, l1, l2))
                      break;
-                  if (kind > 79)
-                     kind = 79;
+                  if (kind > 84)
+                     kind = 84;
                   { jjCheckNAddStates(56, 59); }
                   break;
                case 142:
@@ -2622,8 +2669,8 @@
                      { jjCheckNAddStates(56, 59); }
                   break;
                case 143:
-                  if (jjCanMove_2(hiByte, i1, i2, l1, l2) && kind > 79)
-                     kind = 79;
+                  if (jjCanMove_2(hiByte, i1, i2, l1, l2) && kind > 84)
+                     kind = 84;
                   break;
                default : if (i1 == 0 || l1 == 0 || i2 == 0 ||  l2 == 0) break; else break;
             }
@@ -2751,12 +2798,13 @@
 "\154\145\163\163\124\150\141\156\117\162\105\161\165\141\154\163", 
 "\161\165\141\154\151\146\151\145\144\126\141\154\165\145\123\150\141\160\145", "\161\165\141\154\151\146\151\145\144\115\151\156\103\157\165\156\164", 
 "\161\165\141\154\151\146\151\145\144\115\141\170\103\157\165\156\164", 
-"\161\165\141\154\151\146\151\145\144\126\141\154\165\145\123\150\141\160\145\163\104\151\163\152\157\151\156\164", "\136\136", null, null, null, null, null, null, "\ufeff", null, null, null, 
-null, null, null, null, null, "\53", "\55", "\174", "\100", "\136", "\56", "\41", 
-"\77", "\57", "\52", "\75", "\50", "\51", "\173", "\175", "\133", "\135", null, null, 
+"\161\165\141\154\151\146\151\145\144\126\141\154\165\145\123\150\141\160\145\163\104\151\163\152\157\151\156\164", "\147\162\157\165\160", "\157\162\144\145\162", "\156\141\155\145", 
+"\144\145\163\143\162\151\160\164\151\157\156", "\144\145\146\141\165\154\164\126\141\154\165\145", "\136\136", null, null, 
+null, null, null, null, "\ufeff", null, null, null, null, null, null, null, null, 
+"\53", "\55", "\174", "\100", "\136", "\56", "\41", "\77", "\57", "\52", "\75", 
+"\50", "\51", "\173", "\175", "\133", "\135", null, null, null, null, null, null, 
 null, null, null, null, null, null, null, null, null, null, null, null, null, null, 
-null, null, null, null, null, null, null, null, null, null, null, null, null, null, 
-null, };
+null, null, null, null, null, null, null, null, null, null, null, };
 protected Token jjFillToken()
 {
    final Token t;
@@ -2943,13 +2991,13 @@
    "DEFAULT",
 };
 static final long[] jjtoToken = {
-   0xfeff03ffffffffffL, 0x177cfbffL, 
+   0xdfe07fffffffffffL, 0x2ef9f7fffL, 
 };
 static final long[] jjtoSkip = {
-   0xfc0000000000L, 0x0L, 
+   0x1f800000000000L, 0x0L, 
 };
 static final long[] jjtoSpecial = {
-   0x800000000000L, 0x0L, 
+   0x10000000000000L, 0x0L, 
 };
     protected SimpleCharStream  input_stream;
 
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/CompactOut.java b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/CompactOut.java
similarity index 98%
rename from jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/CompactOut.java
rename to jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/CompactOut.java
index 3f7cbf0..ed6c0f9 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/CompactOut.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/CompactOut.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.jena.shacl.engine.constraint;
+package org.apache.jena.shacl.compact.writer;
 
 import java.util.Collection;
 
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/CompactWriter.java b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/CompactWriter.java
index 83001e1..734b5e8 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/CompactWriter.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/CompactWriter.java
@@ -18,11 +18,13 @@
 
 package org.apache.jena.shacl.compact.writer;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.stream.Collectors;
 
 import org.apache.jena.atlas.io.IndentedLineBuffer;
 import org.apache.jena.atlas.io.IndentedWriter;
+import org.apache.jena.atlas.lib.CollectionUtils;
 import org.apache.jena.riot.out.NodeFormatter;
 import org.apache.jena.riot.out.NodeFormatterTTL;
 import org.apache.jena.riot.system.PrefixMap;
@@ -190,10 +192,10 @@
             return null;
         if ( ! other.getPropertyShapes().isEmpty() )
             return null;
-        List<Constraint> constraints = other.getConstraints();
+        Collection<Constraint> constraints = other.getConstraints();
         if ( constraints.size() != 1 )
             return null;
-        return constraints.get(0);
+        return CollectionUtils.oneElt(constraints);
     }
 
     private static void notShaclc(String string) {
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/ShapeOutputVisitor.java b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/ShapeOutputVisitor.java
index c6d5129..818e646 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/ShapeOutputVisitor.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/compact/writer/ShapeOutputVisitor.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.compact.writer;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.*;
+import static org.apache.jena.shacl.compact.writer.CompactOut.*;
 
 import org.apache.jena.atlas.io.IndentedWriter;
 import org.apache.jena.riot.out.NodeFormatter;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/ValidationContext.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/ValidationContext.java
index 1e34f3e..7dbbc53 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/ValidationContext.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/ValidationContext.java
@@ -20,10 +20,12 @@
 
 import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.Node;
+import org.apache.jena.riot.system.ErrorHandler;
 import org.apache.jena.shacl.Shapes;
 import org.apache.jena.shacl.ValidationReport;
 import org.apache.jena.shacl.parser.Constraint;
 import org.apache.jena.shacl.parser.Shape;
+import org.apache.jena.shacl.sys.ShaclSystem;
 import org.apache.jena.shacl.validation.ReportItem;
 import org.apache.jena.sparql.path.Path;
 
@@ -33,12 +35,19 @@
 
     private final ValidationReport.Builder validationReportBuilder = ValidationReport.create();
     private boolean verbose = false;
+    private boolean seenValidationReportEntry = false;
     private final Shapes shapes;
     private final Graph dataGraph;
     private boolean strict = false;
 
+    private final ErrorHandler errorHandler;
+
     public static ValidationContext create(Shapes shapes, Graph data) {
-        ValidationContext vCxt = new ValidationContext(shapes, data);
+        return create(shapes, data, ShaclSystem.systemShaclErrorHandler);
+    }
+    
+    public static ValidationContext create(Shapes shapes, Graph data, ErrorHandler errorHandler) {
+        ValidationContext vCxt = new ValidationContext(shapes, data, errorHandler);
         vCxt.setVerbose(VERBOSE);
         return vCxt;
     }
@@ -52,11 +61,15 @@
         this.dataGraph = vCxt.dataGraph;
         this.verbose = vCxt.verbose;
         this.strict = vCxt.strict;
+        this.errorHandler = vCxt.errorHandler;
     }
 
-    private ValidationContext(Shapes shapes, Graph data) {
+    private ValidationContext(Shapes shapes, Graph data, ErrorHandler errorHandler) {
         this.shapes = shapes;
         this.dataGraph = data;
+        if ( errorHandler == null )
+            errorHandler = ShaclSystem.systemShaclErrorHandler;
+        this.errorHandler = errorHandler;
         validationReportBuilder.addPrefixes(data.getPrefixMapping());
         validationReportBuilder.addPrefixes(shapes.getGraph().getPrefixMapping());
     }
@@ -70,6 +83,7 @@
     public void reportEntry(String message, Shape shape, Node focusNode, Path path, Node valueNode, Constraint constraint) {
         if ( verbose )
             System.out.println("Validation report entry");
+        seenValidationReportEntry = true;
         validationReportBuilder.addReportEntry(message, shape, focusNode, path, valueNode, constraint);
     }
 
@@ -77,6 +91,8 @@
         return validationReportBuilder.build();
     }
 
+    public boolean hasViolation() { return seenValidationReportEntry; }
+
     public void setVerbose(boolean value) {
         this.verbose = value;
     }
@@ -103,4 +119,9 @@
     public Graph getDataGraph() {
         return dataGraph;
     }
+    
+    public ErrorHandler getErrorHandler() {
+        return errorHandler;
+    }
+
 }
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ClassConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ClassConstraint.java
index e8f1a93..1d34754 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ClassConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ClassConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compact;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compact;
 import static org.apache.jena.shacl.lib.ShLib.displayStr;
 
 import java.util.List;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ClosedConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ClosedConstraint.java
index d808604..6e0fbe3 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ClosedConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ClosedConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.*;
+import static org.apache.jena.shacl.compact.writer.CompactOut.*;
 import static org.apache.jena.shacl.lib.ShLib.displayStr;
 
 import java.util.*;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DatatypeConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DatatypeConstraint.java
index 64d6d07..3285089 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DatatypeConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DatatypeConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compact;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compact;
 import static org.apache.jena.shacl.lib.ShLib.displayStr;
 
 import java.util.Objects;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DisjointConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DisjointConstraint.java
index 14c02de..d2d7bec 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DisjointConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DisjointConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compactArrayNodes;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compactArrayNodes;
 import static org.apache.jena.shacl.lib.ShLib.displayStr;
 
 import java.util.Objects;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/EqualsConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/EqualsConstraint.java
index 673d708..7a3072b 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/EqualsConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/EqualsConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compact;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compact;
 import static org.apache.jena.shacl.lib.ShLib.displayStr;
 
 import java.util.Objects;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/HasValueConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/HasValueConstraint.java
index da50e37..8a18875 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/HasValueConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/HasValueConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compact;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compact;
 import static org.apache.jena.shacl.lib.ShLib.displayStr;
 
 import java.util.Objects;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/InConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/InConstraint.java
index 910040c..cb9bdeb 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/InConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/InConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compactArrayNodes;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compactArrayNodes;
 import static org.apache.jena.shacl.lib.ShLib.displayStr;
 
 import java.util.ArrayList;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JLogConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JLogConstraint.java
new file mode 100644
index 0000000..1a20b89
--- /dev/null
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JLogConstraint.java
@@ -0,0 +1,73 @@
+/*
+ * 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.shacl.engine.constraint;
+
+import java.util.Objects;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.shacl.engine.ValidationContext;
+import org.apache.jena.shacl.lib.ShLib;
+import org.apache.jena.shacl.sys.ShaclSystem;
+import org.apache.jena.shacl.validation.ReportItem;
+import org.apache.jena.shacl.vocabulary.SHJ;
+
+/** A constraint that logs when touched but does not causes a violation */
+public class JLogConstraint extends ConstraintTerm {
+
+    private final String message;
+
+    public JLogConstraint(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public Node getComponent() {
+        return SHJ.LogConstraintComponent;
+    }
+
+    @Override
+    public ReportItem validate(ValidationContext vCxt, Node n) {
+        String msg = String.format("%s[%s]", message, ShLib.displayStr(n));
+        ShaclSystem.systemShaclLogger.warn(msg);
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return "Log["+message+"]";
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(message);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if ( this == obj )
+            return true;
+        if ( obj == null )
+            return false;
+        if ( getClass() != obj.getClass() )
+            return false;
+        JLogConstraint other = (JLogConstraint)obj;
+        return Objects.equals(message, other.message);
+    }
+}
+
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JViolationConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JViolationConstraint.java
new file mode 100644
index 0000000..4aac907
--- /dev/null
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JViolationConstraint.java
@@ -0,0 +1,77 @@
+/*
+ * 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.shacl.engine.constraint;
+
+import org.apache.jena.atlas.io.IndentedWriter;
+import org.apache.jena.graph.Node;
+import org.apache.jena.riot.out.NodeFormatter;
+import org.apache.jena.shacl.compact.writer.CompactOut;
+import org.apache.jena.shacl.engine.ValidationContext;
+import org.apache.jena.shacl.validation.ReportItem;
+import org.apache.jena.shacl.vocabulary.SHJ;
+
+/** A constraint that causes a violation if it's object is "true" */
+public class JViolationConstraint extends ConstraintTerm {
+
+    private final boolean generateViolation;
+
+    public JViolationConstraint(boolean generateViolation) {
+        this.generateViolation = generateViolation;
+    }
+
+    @Override
+    public Node getComponent() {
+        return SHJ.ViolationConstraintComponent;
+    }
+
+    @Override
+    public ReportItem validate(ValidationContext vCxt, Node n) {
+        if ( ! generateViolation )
+            return null;
+        return new ReportItem("Violation");
+    }
+
+    @Override
+    public void printCompact(IndentedWriter out, NodeFormatter nodeFmt) {
+        CompactOut.compactUnquotedString(out, "violation", Boolean.toString(generateViolation));
+    }
+
+    @Override
+    public String toString() {
+        return "Violation["+generateViolation+"]";
+    }
+
+    @Override
+    public int hashCode() {
+        return 158+(generateViolation?1:2);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if ( this == obj )
+            return true;
+        if ( obj == null )
+            return false;
+        if ( getClass() != obj.getClass() )
+            return false;
+        JViolationConstraint other = (JViolationConstraint)obj;
+        return generateViolation == other.generateViolation;
+    }
+}
+
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/LessThanConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/LessThanConstraint.java
index 84ced0a..81a06fa 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/LessThanConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/LessThanConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compact;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compact;
 import static org.apache.jena.shacl.lib.ShLib.displayStr;
 
 import java.util.Objects;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/LessThanOrEqualsConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/LessThanOrEqualsConstraint.java
index a625b6c..0a36e56 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/LessThanOrEqualsConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/LessThanOrEqualsConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compact;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compact;
 import static org.apache.jena.shacl.lib.ShLib.displayStr;
 
 import java.util.Objects;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/PatternConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/PatternConstraint.java
index a2b3db3..c3fc4a5 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/PatternConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/PatternConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.*;
+import static org.apache.jena.shacl.compact.writer.CompactOut.*;
 
 import java.util.Objects;
 import java.util.regex.Pattern;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/QualifiedValueShape.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/QualifiedValueShape.java
index 14199b4..9dc8ddc 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/QualifiedValueShape.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/QualifiedValueShape.java
@@ -26,6 +26,7 @@
 import org.apache.jena.riot.out.NodeFormatter;
 import org.apache.jena.shacl.ShaclException;
 import org.apache.jena.shacl.ValidationReport;
+import org.apache.jena.shacl.compact.writer.CompactOut;
 import org.apache.jena.shacl.compact.writer.CompactWriter;
 import org.apache.jena.shacl.engine.ValidationContext;
 import org.apache.jena.shacl.lib.G;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ReportConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ReportConstraint.java
index e728858..b5f0be2 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ReportConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ReportConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compactQuotedString;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compactQuotedString;
 
 import java.util.Set;
 
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrLanguageIn.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrLanguageIn.java
index 72a28d7..fa9027e 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrLanguageIn.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrLanguageIn.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compactArrayString;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compactArrayString;
 
 import java.util.List;
 import java.util.Objects;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMaxLengthConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMaxLengthConstraint.java
index 2eb8749..73ec972 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMaxLengthConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMaxLengthConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compact;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compact;
 
 import java.util.Objects;
 
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMinLengthConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMinLengthConstraint.java
index e8eb511..4fdc1a0 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMinLengthConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMinLengthConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compact;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compact;
 
 import java.util.Objects;
 
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/UniqueLangConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/UniqueLangConstraint.java
index 608ada5..494df61 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/UniqueLangConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/UniqueLangConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compactUnquotedString;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compactUnquotedString;
 
 import java.util.HashSet;
 import java.util.Objects;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ValueRangeConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ValueRangeConstraint.java
index f5c2b62..fddd75c 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ValueRangeConstraint.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ValueRangeConstraint.java
@@ -18,7 +18,7 @@
 
 package org.apache.jena.shacl.engine.constraint;
 
-import static org.apache.jena.shacl.engine.constraint.CompactOut.compact;
+import static org.apache.jena.shacl.compact.writer.CompactOut.compact;
 
 import java.util.Objects;
 
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Constraints.java b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Constraints.java
index 5a1929b..5d75798 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Constraints.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Constraints.java
@@ -84,35 +84,35 @@
         dispatch.put( SHACL.nodeKind,          (g, s, p, o) -> new NodeKindConstraint(o) );
         dispatch.put( SHACL.minCount,          (g, s, p, o) -> new MinCount(intValue(o)) );
         dispatch.put( SHACL.maxCount,          (g, s, p, o) -> new MaxCount(intValue(o)) );
-    
+
         dispatch.put( SHACL.minInclusive,      (g, s, p, o) -> new ValueMinInclusiveConstraint(o) );
         dispatch.put( SHACL.minExclusive,      (g, s, p, o) -> new ValueMinExclusiveConstraint(o) );
         dispatch.put( SHACL.maxInclusive,      (g, s, p, o) -> new ValueMaxInclusiveConstraint(o) );
         dispatch.put( SHACL.maxExclusive,      (g, s, p, o) -> new ValueMaxExclusiveConstraint(o) );
-    
+
         dispatch.put( SHACL.minLength,         (g, s, p, o) -> new StrMinLengthConstraint(intValue(o)) );
         dispatch.put( SHACL.maxLength,         (g, s, p, o) -> new StrMaxLengthConstraint(intValue(o)) );
         // in parseConstraint
         //dispatch.put( SHACL.pattern,           (g, p, o) -> notImplemented(p) );
         dispatch.put( SHACL.languageIn,        (g, s, p, o) -> new StrLanguageIn(listString(g, o)) );
         dispatch.put( SHACL.uniqueLang,        (g, s, p, o) -> new UniqueLangConstraint(booleanValueStrict(o)) );
-    
+
         dispatch.put( SHACL.hasValue,          (g, s, p, o) -> new HasValueConstraint(o) );
         dispatch.put( SHACL.in,                (g, s, p, o) -> new InConstraint(list(g,o)) );
         dispatch.put( SHACL.closed,            (g, s, p, o) -> new ClosedConstraint(g,s,booleanValue(o)) );
-    
+
         dispatch.put( SHACL.equals,            (g, s, p, o) -> new EqualsConstraint(o) );
         dispatch.put( SHACL.disjoint,          (g, s, p, o) -> new DisjointConstraint(o) );
         dispatch.put( SHACL.lessThan,          (g, s, p, o) -> new LessThanConstraint(o) );
         dispatch.put( SHACL.lessThanOrEquals,  (g, s, p, o) -> new LessThanOrEqualsConstraint(o) );
-    
+
         // Below
         //dispatch.put( SHACL.not,                (g, s, p, o) -> notImplemented(p) );
         //dispatch.put( SHACL.and,                (g, s, p, o) -> notImplemented(p) );
         //dispatch.put( SHACL.or,                 (g, s, p, o) -> notImplemented(p) );
         //dispatch.put( SHACL.xone,               (g, s, p, o) -> notImplemented(p) );
         //dispatch.put( SHACL.node,               (g, s, p, o) -> notImplemented(p) );
-    
+
         dispatch.put(SHACL.sparql, (g, s, p, o) -> SparqlConstraints.parseSparqlConstraint(g, s, p, o) );
     }
 
@@ -144,7 +144,7 @@
      * parser at when the constraint uses other shapes
      * (sh:and/sh:or/sh:not/sh:xone.sh:node).
      */
-    /*package*/ static List<Constraint> parseConstraints(Graph shapesGraph, Node shape, Map<Node, Shape> parsed) {
+    /*package*/ static List<Constraint> parseConstraints(Graph shapesGraph, Node shape, Map<Node, Shape> parsed, Set<Node> traversed) {
         List<Constraint> constraints = new ArrayList<>();
         Iterator<Triple> iter = G.find(shapesGraph, shape, null, null);
         while(iter.hasNext()) {
@@ -157,18 +157,18 @@
                 continue;
             Node s = t.getSubject();
             Node o = t.getObject();
-            Constraint c = parseConstraint(shapesGraph, s, p, o, parsed);
+            Constraint c = parseConstraint(shapesGraph, s, p, o, parsed, traversed);
             if ( c != null )
                 constraints.add(c);
         }
         return constraints;
     }
 
-    /** 
-     * The translate of an RDF triple into a {@link Constraint}. 
+    /**
+     * The translate of an RDF triple into a {@link Constraint}.
      * Constraints require more that just the triple being inspected.
-     */  
-    private static Constraint parseConstraint(Graph g, Node s, Node p, Node o, Map<Node, Shape> parsed) {
+     */
+    private static Constraint parseConstraint(Graph g, Node s, Node p, Node o, Map<Node, Shape> parsed, Set<Node> traversed) {
 
         // Test for single triple constraints.
         ConstraintMaker maker = dispatch.get(p);
@@ -177,35 +177,35 @@
 
         // These require the "parsed" map.
         if ( p.equals(SHACL.not) ) {
-            Shape shape = ShapesParser.parseShapeStep(parsed, g, o);
+            Shape shape = ShapesParser.parseShapeStep(traversed, parsed, g, o);
             return new ShNot(shape);
         }
 
         if ( p.equals(SHACL.or) ) {
             List<Node> elts = list(g, o);
-            List<Shape> shapes = elts.stream().map(x->ShapesParser.parseShapeStep(parsed, g, x)).collect(Collectors.toList());
+            List<Shape> shapes = elts.stream().map(x->ShapesParser.parseShapeStep(traversed, parsed, g, x)).collect(Collectors.toList());
             return new ShOr(shapes);
         }
         if ( p.equals(SHACL.and) ) {
             List<Node> elts = list(g, o);
-            List<Shape> shapes = elts.stream().map(x->ShapesParser.parseShapeStep(parsed, g, x)).collect(Collectors.toList());
+            List<Shape> shapes = elts.stream().map(x->ShapesParser.parseShapeStep(traversed, parsed, g, x)).collect(Collectors.toList());
             return new ShAnd(shapes);
         }
 
         if ( p.equals(SHACL.xone) ) {
             List<Node> elts = list(g, o);
-            List<Shape> shapes = elts.stream().map(x->ShapesParser.parseShapeStep(parsed, g, x)).collect(Collectors.toList());
+            List<Shape> shapes = elts.stream().map(x->ShapesParser.parseShapeStep(traversed, parsed, g, x)).collect(Collectors.toList());
             return new ShXone(shapes);
         }
 
         if ( p.equals(SHACL.node) ) {
-            Shape other = ShapesParser.parseShapeStep(parsed, g, o);
+            Shape other = ShapesParser.parseShapeStep(traversed, parsed, g, o);
             if ( other instanceof PropertyShape )
                 throw new ShaclParseException("Object of sh:node must be a node shape, not a property shape");
             return new ShNode(other);
         }
 
-        // sh:pattern is influenced by an adjacent sh:flags. 
+        // sh:pattern is influenced by an adjacent sh:flags.
         if ( p.equals(SHACL.pattern) ) {
             Node pat = o;
             if ( ! Util.isSimpleString(pat) )
@@ -221,7 +221,7 @@
             return null;
 
         if ( p.equals(SHACL.qualifiedValueShape) )
-            return parseQualifiedValueShape(g, s, p, o, parsed);
+            return parseQualifiedValueShape(g, s, p, o, parsed, traversed);
 
         // sh:qualifiedValueShape parameters.
         if ( p.equals(SHACL.QualifiedMinCountConstraintComponent) ||
@@ -244,8 +244,8 @@
         return null;
     }
 
-    private static Constraint parseQualifiedValueShape(Graph g, Node s, Node p, Node o, Map<Node, Shape> parsed) {
-        Shape sub = ShapesParser.parseShapeStep(parsed, g, o);
+    private static Constraint parseQualifiedValueShape(Graph g, Node s, Node p, Node o, Map<Node, Shape> parsed, Set<Node> traversed) {
+        Shape sub = ShapesParser.parseShapeStep(traversed, parsed, g, o);
         // [PARSE] Syntax check needed
         Node qMin = G.getZeroOrOneSP(g, s, SHACL.qualifiedMinCount);
         Node qMax = G.getZeroOrOneSP(g, s, SHACL.qualifiedMaxCount);
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/NodeShape.java b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/NodeShape.java
index 8cc7d30..e65629e 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/NodeShape.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/NodeShape.java
@@ -19,7 +19,6 @@
 package org.apache.jena.shacl.parser;
 
 import java.util.Collection;
-import java.util.List;
 
 import org.apache.jena.atlas.io.IndentedWriter;
 import org.apache.jena.graph.Graph;
@@ -31,8 +30,8 @@
 public class NodeShape extends Shape {
 
     public NodeShape(Graph shapeGraph, Node shapeNode, boolean deactivated,
-                     Severity severity, List<Node> messages, Collection<Target> targets,
-                     List<Constraint> constraints, List<PropertyShape> propertyShapes) {
+                     Severity severity, Collection<Node> messages, Collection<Target> targets,
+                     Collection<Constraint> constraints, Collection<PropertyShape> propertyShapes) {
         super(shapeGraph, shapeNode, deactivated, severity, messages, targets, constraints, propertyShapes);
     }
 
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/PropertyShape.java b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/PropertyShape.java
index f6c13f8..6259011 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/PropertyShape.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/PropertyShape.java
@@ -34,7 +34,7 @@
 public class PropertyShape extends Shape {
 
     private final Path path;
-    public PropertyShape(Graph shapeGraph, Node shapeNode, boolean isDeactivated, Severity severity, List<Node> messages,
+    public PropertyShape(Graph shapeGraph, Node shapeNode, boolean isDeactivated, Severity severity, Collection<Node> messages,
                          Collection<Target> targets, Path path, List<Constraint> constraints, List<PropertyShape> propertyShapes) {
         super(shapeGraph, shapeNode, isDeactivated, severity, messages, targets, constraints, propertyShapes);
         this.path = Objects.requireNonNull(path, "path");
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Shape.java b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Shape.java
index 7d725fd..74c33d4 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Shape.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Shape.java
@@ -21,7 +21,6 @@
 import java.io.BufferedOutputStream;
 import java.io.OutputStream;
 import java.util.Collection;
-import java.util.List;
 
 import org.apache.jena.atlas.io.IndentedWriter;
 import org.apache.jena.graph.Graph;
@@ -40,11 +39,11 @@
     protected final Severity            severity;
     protected final Collection<Node>    messages;
     protected final Collection<Target>  targets;
-    protected final List<Constraint>    constraints;
-    protected final List<PropertyShape> propertyShapes;
+    protected final Collection<Constraint>    constraints;
+    protected final Collection<PropertyShape> propertyShapes;
 
-    public Shape(Graph shapeGraph, Node shapeNode, boolean deactivated, Severity severity, List<Node> messages,
-                 Collection<Target> targets, List<Constraint> constraints, List<PropertyShape> propertyShapes) {
+    protected Shape(Graph shapeGraph, Node shapeNode, boolean deactivated, Severity severity, Collection<Node> messages,
+                 Collection<Target> targets, Collection<Constraint> constraints, Collection<PropertyShape> propertyShapes) {
         super();
         this.shapeGraph = shapeGraph;
         this.shapeNode = shapeNode;
@@ -82,11 +81,11 @@
         return ! targets.isEmpty();
     }
 
-    public List<Constraint> getConstraints() {
+    public Collection<Constraint> getConstraints() {
         return constraints;
     }
 
-    public List<PropertyShape> getPropertyShapes() {
+    public Collection<PropertyShape> getPropertyShapes() {
         return propertyShapes;
     }
 
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/ShapesParser.java b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/ShapesParser.java
index c201159..ed8f49e 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/ShapesParser.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/ShapesParser.java
@@ -36,7 +36,10 @@
 import org.apache.jena.shacl.engine.Target;
 import org.apache.jena.shacl.engine.TargetType;
 import org.apache.jena.shacl.engine.Targets;
+import org.apache.jena.shacl.engine.constraint.JLogConstraint;
 import org.apache.jena.shacl.lib.G;
+import org.apache.jena.shacl.lib.ShLib;
+import org.apache.jena.shacl.sys.ShaclSystem;
 import org.apache.jena.shacl.validation.Severity;
 import org.apache.jena.shacl.vocabulary.SHACL;
 import org.apache.jena.shared.JenaException;
@@ -58,7 +61,6 @@
 
     private static final boolean DEBUG = false;
     private static IndentedWriter OUT = IndentedWriter.stdout;
-    //private static Logger LOG = LoggerFactory.getLogger(ShapesParser.class);
 
     /**
      * Parse, starting from the given targets.
@@ -67,6 +69,13 @@
      * Applications should call functions in {@link Shapes} rather than call the parser directly.
      */
     public static Collection<Shape> parseShapes(Graph shapesGraph, Targets targets, Map<Node, Shape> shapesMap) {
+        // Cycle detection. 
+        Set<Node> cycles = new HashSet<>();
+        return parseShapes(shapesGraph, targets, shapesMap, cycles);
+    }
+    
+    /*package*/ static Collection<Shape> parseShapes(Graph shapesGraph, Targets targets, Map<Node, Shape> shapesMap, Set<Node> cycles) {
+        
         Targets rootShapes = targets;
 
         if ( DEBUG )
@@ -163,15 +172,17 @@
 
     // ---- Main parser worker.
     /**
-     *  Parse shape "shNode", updating the record of shapes already parsed.
+     *  Parse one shape updating the record of shapes already parsed.
      *
      * @param shapesMap
      * @param shapesGraph
      * @param shNode
      * @return Shape
      */
+    
     public static Shape parseShape(Map<Node, Shape> shapesMap, Graph shapesGraph, Node shNode) {
-        Shape shape = parseShapeStep(shapesMap, shapesGraph, shNode);
+        Set<Node> traversed = new HashSet<>();
+        Shape shape = parseShapeStep(traversed, shapesMap, shapesGraph, shNode);
         return shape;
     }
 
@@ -218,22 +229,42 @@
     | sh:path         |
     -------------------
      */
+    
+    /** Do nothing placeholder shape. */
+    static Shape unshape(Graph shapesGraph, Node shapeNode) { return 
+            new NodeShape(shapesGraph, shapeNode, false, Severity.Violation,
+                          Collections.emptySet(), Collections.emptySet(),
+                          Collections.singleton(new JLogConstraint("Cycle")),
+                          Collections.emptySet());
+    }
+    
     /** parse a shape during a parsing process */
-    /*package*/ static Shape parseShapeStep(Map<Node, Shape> parsed, Graph shapesGraph, Node shapeNode) {
+    /*package*/ static Shape parseShapeStep(Set<Node> traversed, Map<Node, Shape> parsed, Graph shapesGraph, Node shapeNode) {
         // Called by Constraints
         if ( parsed.containsKey(shapeNode) )
             return parsed.get(shapeNode);
-        Shape shape = parseShape$(parsed, shapesGraph, shapeNode);
+        // Loop detection. Do before parsing.
+        if ( traversed.contains(shapeNode) ) {
+//            Log.error(ShapesParser.class,  "Cycle detected : node "+ShLib.displayStr(shapeNode));
+//            throw new ShaclParseException("Shapes cycle detected : node "+ShLib.displayStr(shapeNode));
+            ShaclSystem.systemShaclLogger.warn("Cycle detected : node "+ShLib.displayStr(shapeNode));
+            // Put in a substitute shape.
+            return unshape(shapesGraph, shapeNode);
+        }
+        
+        traversed.add(shapeNode);
+        Shape shape = parseShape$(traversed, parsed, shapesGraph, shapeNode);
         parsed.put(shapeNode, shape);
+        traversed.remove(shapeNode);
         return shape;
     }
 
-    private static Shape parseShape$(Map<Node, Shape> parsed, Graph shapesGraph, Node shapeNode) {
+    private static Shape parseShape$(Set<Node> traversed, Map<Node, Shape> parsed, Graph shapesGraph, Node shapeNode) {
         if ( DEBUG )
             OUT.printf("Parse shape : %s\n", displayStr(shapeNode));
         boolean isDeactivated = absentOrOne(shapesGraph, shapeNode, SHACL.deactivated, NodeConst.nodeTrue);
         Collection<Target> targets = targets(shapesGraph, shapeNode);
-        List<Constraint> constraints = Constraints.parseConstraints(shapesGraph, shapeNode, parsed);
+        List<Constraint> constraints = Constraints.parseConstraints(shapesGraph, shapeNode, parsed, traversed);
         Severity severity = severity(shapesGraph, shapeNode);
         List<Node> messages = listSP(shapesGraph, shapeNode, SHACL.message);
 
@@ -241,7 +272,7 @@
             OUT.incIndent();
         // sh:Property PropertyShapes from this shape.
         // sh:node is treated as a constraint.
-        List<PropertyShape> propertyShapes = findPropertyShapes(parsed, shapesGraph, shapeNode);
+        List<PropertyShape> propertyShapes = findPropertyShapes(traversed, parsed, shapesGraph, shapeNode);
         if ( DEBUG )
             OUT.decIndent();
 
@@ -303,7 +334,7 @@
         return ShaclPaths.parsePath(shapesGraph, node);
     }
 
-    private static List<PropertyShape> findPropertyShapes(Map<Node, Shape> parsed, Graph shapesGraph, Node shapeNode) {
+    private static List<PropertyShape> findPropertyShapes(Set<Node> traversed, Map<Node, Shape> parsed, Graph shapesGraph, Node shapeNode) {
         List<Triple> propertyTriples = G.find(shapesGraph, shapeNode, SHACL.property, null).toList();
         List<PropertyShape> propertyShapes = new ArrayList<>();
         for ( Triple t : propertyTriples) {
@@ -321,9 +352,9 @@
             }
             if ( x > 1 ) {
                 List<Node> paths = listSP(shapesGraph, propertyShape, SHACL.path);
-                throw new ShaclParseException("Muiltiple sh:path on a property shape: "+displayStr(shapeNode)+" sh:property"+displayStr(propertyShape)+ " : "+paths);
+                throw new ShaclParseException("Multiple sh:path on a property shape: "+displayStr(shapeNode)+" sh:property"+displayStr(propertyShape)+ " : "+paths);
             }
-            PropertyShape ps = (PropertyShape)parseShapeStep(parsed, shapesGraph, propertyShape);
+            PropertyShape ps = (PropertyShape)parseShapeStep(traversed, parsed, shapesGraph, propertyShape);
             propertyShapes.add(ps);
         }
         return propertyShapes;
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/sys/ShaclSystem.java b/jena-shacl/src/main/java/org/apache/jena/shacl/sys/ShaclSystem.java
index 01ae3e9..73c32a8 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/sys/ShaclSystem.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/sys/ShaclSystem.java
@@ -18,10 +18,17 @@
 
 package org.apache.jena.shacl.sys;
 
+import org.apache.jena.riot.system.ErrorHandler;
+import org.apache.jena.riot.system.ErrorHandlerFactory;
 import org.apache.jena.shacl.ShaclValidator;
 import org.apache.jena.shacl.validation.ShaclPlainValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class ShaclSystem {
+    public static Logger systemShaclLogger = LoggerFactory.getLogger("SHACL"); 
+    public static ErrorHandler systemShaclErrorHandler = ErrorHandlerFactory.errorHandlerStd(systemShaclLogger);
+    
     private static ShaclValidator globalDefault = new ShaclPlainValidator();
     
     /** Set the current system-wide {@link ShaclValidator}. */
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/validation/VLib.java b/jena-shacl/src/main/java/org/apache/jena/shacl/validation/VLib.java
index 184382d..451fe35 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/validation/VLib.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/validation/VLib.java
@@ -121,7 +121,7 @@
             out.println();
     }
 
-    static void validationPropertyShapes(ValidationContext vCxt, Graph data, List<PropertyShape> propertyShapes, Node focusNode) {
+    static void validationPropertyShapes(ValidationContext vCxt, Graph data, Collection<PropertyShape> propertyShapes, Node focusNode) {
         if ( propertyShapes == null )
             return;
         for ( PropertyShape propertyShape : propertyShapes ) {
diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/vocabulary/SHJ.java b/jena-shacl/src/main/java/org/apache/jena/shacl/vocabulary/SHJ.java
index 061db14..3033b67 100644
--- a/jena-shacl/src/main/java/org/apache/jena/shacl/vocabulary/SHJ.java
+++ b/jena-shacl/src/main/java/org/apache/jena/shacl/vocabulary/SHJ.java
@@ -18,17 +18,35 @@
 
 package org.apache.jena.shacl.vocabulary;
 
+import java.util.Objects;
+
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.NodeFactory;
 
 /** Vocabulary for Jena additions to SHACL */
 public class SHJ {
-    private static Node createResource(String localName) { return NodeFactory.createURI(NS+localName); }
-    private static Node createProperty(String localName) { return NodeFactory.createURI(NS+localName); }
 
     /** The namespace of the vocabulary as a string */
     public static final String NS = "http://jena.apache.org/shacl#";
 
     /** The namespace of the vocabulary as a string*/
     public static String getURI() {return NS;}
+    
+    /** Namespace */
+    public String ns() { return NS; }
+        
+    private static String uri(String ns, String local) {
+        Objects.requireNonNull(ns);
+        Objects.requireNonNull(local);
+        return ns+local;
+    }
+
+    private static Node createResource(String ns, String localName) { return NodeFactory.createURI(uri(ns, localName)); }
+    private static Node createProperty(String ns, String localName) { return NodeFactory.createURI(uri(ns, localName)); }
+
+    public static final Node LogConstraintComponent         = createResource(NS, "LogConstraintComponent");
+    public static final Node logConstraint                  = createProperty(NS, "log");
+
+    public static final Node ViolationConstraintComponent   = createResource(NS, "ViolationConstraintComponent");
+    public static final Node violation                      = createProperty(NS, "violation");
 }
diff --git a/jena-tdb/src/main/java/org/apache/jena/tdb/solver/SolverLib.java b/jena-tdb/src/main/java/org/apache/jena/tdb/solver/SolverLib.java
index 0539c12..45145fb 100644
--- a/jena-tdb/src/main/java/org/apache/jena/tdb/solver/SolverLib.java
+++ b/jena-tdb/src/main/java/org/apache/jena/tdb/solver/SolverLib.java
@@ -256,7 +256,7 @@
                 // Can occur with BindingProject
                 continue ;
 
-            // Rely on the node table cache for efficency - we will likely be
+            // Rely on the node table cache for efficiency - we will likely be
             // repeatedly looking up the same node in different bindings.
             NodeId id = nodeTable.getNodeIdForNode(n) ;
             // Optional: whether to put in "known missing"
@@ -312,7 +312,6 @@
 
         final Var var = Var.alloc(graphNode) ;
         Iterator<Binding> iterBinding = Iter.map(iter4, node -> BindingFactory.binding(var, node)) ;
-        // Not abortable.
         return new QueryIterTDB(iterBinding, killList, input, execCxt) ;
     }
     
diff --git a/jena-tdb/src/main/java/org/apache/jena/tdb/solver/SolverRX.java b/jena-tdb/src/main/java/org/apache/jena/tdb/solver/SolverRX.java
index 7b661f6..93e62ad 100644
--- a/jena-tdb/src/main/java/org/apache/jena/tdb/solver/SolverRX.java
+++ b/jena-tdb/src/main/java/org/apache/jena/tdb/solver/SolverRX.java
@@ -69,7 +69,7 @@
         VarAlloc varAlloc = VarAlloc.get(context, ARQConstants.sysVarAllocRDFStar);
         if ( varAlloc == null ) {
             varAlloc = new VarAlloc(ARQConstants.allocVarTripleTerm);
-            context.set(ARQConstants.sysVarAllocRDFStar, varAlloc);  
+            context.set(ARQConstants.sysVarAllocRDFStar, varAlloc);
         }
         return varAlloc;
     }
@@ -80,7 +80,7 @@
                                                   ExecutionContext execCxt) {
         if ( ! tripleHasNodeTriple(pattern) )
             SolverLib.solve(nodeTupleTable, pattern, anyGraph, chain, filter, execCxt);
-        
+
         Args args = new Args(nodeTupleTable, anyGraph, filter, execCxt);
         return rdfStarTriple(chain, pattern, args);
     }
@@ -92,7 +92,7 @@
      * within {@link #rdfStarTriple} for nested triple term and a temporary allocated
      * variable as well can for {@code FIND(<<...>> AS ?t)}.
      *
-     * @implNote 
+     * @implNote
      * Without RDF*, this would be a plain call of {@link #matchData} which
      * is simply a call to {@link SolverLib#solve}.
      */
@@ -148,7 +148,7 @@
         Node subject1 = null;
         Node object1 = null;
 
-        if ( subject.isNodeTriple() && ! subject.isConcrete() ) {
+        if ( subject.isNodeTriple() ) {
             Triple tripleTerm = triple(subject);
             Var var = args.varAlloc.allocVar();
             patternTuple = createTuple(patternTuple, var, sIdx);
@@ -157,7 +157,7 @@
             subject1 = var;
         }
 
-        if ( object.isNodeTriple() && ! object.isConcrete() ) {
+        if ( object.isNodeTriple() ) {
             Triple tripleTerm = triple(object);
             Var var = args.varAlloc.allocVar();
             patternTuple = createTuple(patternTuple, var, oIdx);
@@ -221,7 +221,7 @@
                 return binding;
             return null;
         }
-        
+
         BindingNodeId b2 = new BindingNodeId(binding);
         b2.put(var, tid);
         return b2;
@@ -305,11 +305,11 @@
 
     private static Tuple<Node> tuple(Tuple<Node> base, Triple triple) {
         switch(base.len()){
-            case 3: 
-                return TupleFactory.create3(triple.getSubject(), triple.getPredicate(), triple.getObject());   
+            case 3:
+                return TupleFactory.create3(triple.getSubject(), triple.getPredicate(), triple.getObject());
             case 4:
                 return TupleFactory.create4(base.get(0), triple.getSubject(), triple.getPredicate(), triple.getObject());
             default:
-        }       throw new TDBException("Tuple not of length 3 or 4"); 
+        }       throw new TDBException("Tuple not of length 3 or 4");
     }
 }
diff --git a/pom.xml b/pom.xml
index 91074ce..2f28792 100644
--- a/pom.xml
+++ b/pom.xml
@@ -70,7 +70,7 @@
          POM for the correct dependency versions
          and use that or later.
     -->
-    <ver.jsonldjava>0.12.5</ver.jsonldjava>
+    <ver.jsonldjava>0.13.1</ver.jsonldjava>
     <ver.jackson>2.10.1</ver.jackson>
     <ver.jackson-databind>${ver.jackson}</ver.jackson-databind>