You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2015/10/23 22:38:11 UTC

[10/13] jena git commit: JENA-1055: Rationalize method checking handling especially OPTIONS.

JENA-1055: Rationalize method checking handling especially OPTIONS.

Project: http://git-wip-us.apache.org/repos/asf/jena/repo
Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/d61d1ca2
Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/d61d1ca2
Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/d61d1ca2

Branch: refs/heads/master
Commit: d61d1ca27af8023b59bfb4664a88b245e8e17e83
Parents: e9ef60b
Author: Andy Seaborne <an...@apache.org>
Authored: Fri Oct 23 19:50:18 2015 +0100
Committer: Andy Seaborne <an...@apache.org>
Committed: Fri Oct 23 19:50:18 2015 +0100

----------------------------------------------------------------------
 .../org/apache/jena/fuseki/build/Builder.java   |  20 +--
 .../apache/jena/fuseki/build/FusekiConfig.java  |   1 +
 .../org/apache/jena/fuseki/mgt/ActionStats.java |   2 +-
 .../apache/jena/fuseki/servlets/REST_Quads.java |  10 +-
 .../jena/fuseki/servlets/REST_Quads_R.java      |  13 +-
 .../jena/fuseki/servlets/REST_Quads_RW.java     |  14 +-
 .../jena/fuseki/servlets/SPARQL_GSP_R.java      |   8 +-
 .../fuseki/servlets/SPARQL_UberServlet.java     |  67 +++++-----
 .../apache/jena/fuseki/servlets/ServletOps.java |   2 +
 .../java/org/apache/jena/fuseki/TS_Fuseki.java  |   2 +
 .../org/apache/jena/fuseki/TestHttpOptions.java |  61 +++++++++
 .../apache/jena/fuseki/TestServerReadOnly.java  | 134 +++++++++++++++++++
 12 files changed, 275 insertions(+), 59 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Builder.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Builder.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Builder.java
index 77297c7..9b15ec1 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Builder.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Builder.java
@@ -44,8 +44,9 @@ public class Builder
 {
     private static Logger log = Fuseki.builderLog ;
     
-    /** Build a DataAccessPoint, including DataServiceat Resource svc */
+    /** Build a DataAccessPoint, including DataService at Resource svc */ 
     public static DataAccessPoint buildDataAccessPoint(Resource svc) {
+        // XXX
         RDFNode n = FusekiLib.getOne(svc, "fu:name") ;
         if ( ! n.isLiteral() )
             throw new FusekiConfigException("Not a literal for access point name: "+FmtUtils.stringForRDFNode(n));
@@ -63,7 +64,7 @@ public class Builder
     }
 
     /** Build a DatasetRef starting at Resource svc */
-    public static DataService buildDataService(Resource svc) {
+    private static DataService buildDataService(Resource svc) {
         //log.debug("Service: " + nodeLabel(svc)) ;
         // DO REAL WORK
         Resource datasetDesc = ((Resource)getOne(svc, "fu:dataset")) ;
@@ -78,12 +79,12 @@ public class Builder
         addServiceEP(dataService, OperationName.Update, svc,    "fu:serviceUpdate") ;
         addServiceEP(dataService, OperationName.Upload, svc,    "fu:serviceUpload") ;
         addServiceEP(dataService, OperationName.GSP_R,  svc,    "fu:serviceReadGraphStore") ;
-        addServiceEP(dataService, OperationName.GSP,    svc,    "fu:serviceReadWriteGraphStore") ;
+        addServiceEP(dataService, OperationName.GSP_RW, svc,    "fu:serviceReadWriteGraphStore") ;
         
-        if ( ! dataService.getOperation(OperationName.GSP).isEmpty() )
-            dataService.addEndpoint(OperationName.Quads, "") ;
+        if ( ! dataService.getOperation(OperationName.GSP_RW).isEmpty() )
+            dataService.addEndpoint(OperationName.Quads_RW, "") ;
         else if ( ! dataService.getOperation(OperationName.GSP_R).isEmpty() )
-            dataService.addEndpoint(OperationName.Quads, "") ;
+            dataService.addEndpoint(OperationName.Quads_R, "") ;
         
         // XXX 
 //        // Extract timeout overriding configuration if present.
@@ -103,14 +104,15 @@ public class Builder
         addServiceEP(dataService, OperationName.Query, "query") ;
         addServiceEP(dataService, OperationName.Query, "sparql") ;
         if ( ! allowUpdate ) {
-            addServiceEP(dataService, OperationName.Quads, "quads") ;
             addServiceEP(dataService, OperationName.GSP_R, "data") ;
+            addServiceEP(dataService, OperationName.Quads_R, "") ;
             return dataService ;
         }
-        addServiceEP(dataService, OperationName.GSP,    "data") ;
+        addServiceEP(dataService, OperationName.GSP_RW,    "data") ;
+        addServiceEP(dataService, OperationName.GSP_R,  "get") ;
         addServiceEP(dataService, OperationName.Update, "update") ;
         addServiceEP(dataService, OperationName.Upload, "upload") ;
-        addServiceEP(dataService, OperationName.Quads,  "") ;
+        addServiceEP(dataService, OperationName.Quads_RW,  "") ;
         return dataService ;
     }
 

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
----------------------------------------------------------------------
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 8bfa469..8bd4afd 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
@@ -245,6 +245,7 @@ public class FusekiConfig {
         }
 
         Resource service = services.get(0) ;
+        // Configuration file determines read-only status.
         DataAccessPoint acc = Builder.buildDataAccessPoint(service) ; 
         return acc ;
     }

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java
index 4e9a082..24e1c5b 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java
@@ -187,7 +187,7 @@ public class ActionStats extends ActionContainerItem
     }
     
     private long gspValue(DataService dSrv, CounterName cn) {
-        return  counter(dSrv, OperationName.GSP, cn) +
+        return  counter(dSrv, OperationName.GSP_RW, cn) +
                 counter(dSrv, OperationName.GSP_R, cn) ;
     }
 

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads.java
index 578447e..8fb1a7d 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads.java
@@ -24,19 +24,13 @@ package org.apache.jena.fuseki.servlets ;
  * dataset URI.
  */
 
-public abstract class REST_Quads extends SPARQL_GSP {
-    // Not supported: GSP direct naming.
-
+public abstract class REST_Quads extends SPARQL_GSP
+{
     public REST_Quads() {
         super() ;
     }
 
     @Override
-    protected void validate(HttpAction action) {
-        // Check in the operations itself.
-    }
-
-    @Override
     protected void doOptions(HttpAction action) {
         ServletOps.errorMethodNotAllowed("OPTIONS") ;
     }

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads_R.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads_R.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads_R.java
index a8ce180..73e2715 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads_R.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads_R.java
@@ -41,7 +41,18 @@ public class REST_Quads_R extends REST_Quads {
     }
 
     @Override
-    protected void validate(HttpAction action) { }
+    protected void validate(HttpAction action) { 
+        // Allowed methods controlled by ActionREST.dispatch
+        String method = action.getRequest().getMethod() ;
+        switch(method) {
+            case HttpNames.METHOD_GET:
+            case HttpNames.METHOD_HEAD:
+            case HttpNames.METHOD_OPTIONS:
+                break ;
+            default:
+                ServletOps.errorMethodNotAllowed(method+" : Read-only dataset");
+        }
+    }
 
     @Override
     protected void doGet(HttpAction action) {

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads_RW.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads_RW.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads_RW.java
index 80b1b8a..c845d7b 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads_RW.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/REST_Quads_RW.java
@@ -22,6 +22,7 @@ import org.apache.jena.fuseki.FusekiLib ;
 import org.apache.jena.riot.RiotException ;
 import org.apache.jena.riot.system.StreamRDF ;
 import org.apache.jena.riot.system.StreamRDFLib ;
+import org.apache.jena.riot.web.HttpNames ;
 import org.apache.jena.sparql.core.DatasetGraph ;
 import org.apache.jena.sparql.core.DatasetGraphFactory ;
 
@@ -37,8 +38,17 @@ public class REST_Quads_RW extends REST_Quads_R {
     }
 
     @Override
-    protected void validate(HttpAction action) { }
-
+    protected void validate(HttpAction action) { 
+    }
+    
+    @Override
+    protected void doOptions(HttpAction action) {
+        setCommonHeadersForOptions(action.response) ;
+        action.response.setHeader(HttpNames.hAllow, "GET,HEAD,OPTIONS,PUT,POST");
+        action.response.setHeader(HttpNames.hContentLengh, "0") ;
+        ServletOps.success(action) ;
+    }
+    
     @Override
     protected void doPost(HttpAction action) {
         if ( !action.getDataService().allowUpdate() )

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_GSP_R.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_GSP_R.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_GSP_R.java
index f35e582..54d04d5 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_GSP_R.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_GSP_R.java
@@ -106,17 +106,17 @@ public class SPARQL_GSP_R extends SPARQL_GSP
 
     @Override
     protected void doPost(HttpAction action)
-    { ServletOps.errorMethodNotAllowed("POST") ; }
+    { ServletOps.errorMethodNotAllowed("POST : Read-only") ; }
 
     @Override
     protected void doDelete(HttpAction action)
-    { ServletOps.errorMethodNotAllowed("DELETE") ; }
+    { ServletOps.errorMethodNotAllowed("DELETE : Read-only") ; }
 
     @Override
     protected void doPut(HttpAction action)
-    { ServletOps.errorMethodNotAllowed("PUT") ; }
+    { ServletOps.errorMethodNotAllowed("PUT : Read-only") ; }
 
     @Override
     protected void doPatch(HttpAction action)
-    { ServletOps.errorMethodNotAllowed("PATCH") ; }
+    { ServletOps.errorMethodNotAllowed("PATCH : Read-only") ; }
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java
index f64e7c3..1a49956 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java
@@ -22,8 +22,10 @@ import static java.lang.String.format ;
 import static org.apache.jena.riot.WebContent.contentTypeSPARQLQuery ;
 import static org.apache.jena.riot.WebContent.contentTypeSPARQLUpdate ;
 
+import java.io.IOException ;
 import java.util.List ;
 
+import javax.servlet.ServletException ;
 import javax.servlet.http.HttpServletRequest ;
 import javax.servlet.http.HttpServletResponse ;
 
@@ -80,11 +82,10 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL
         public AccessByConfig()    { super() ; }
         @Override protected boolean allowQuery(HttpAction action)    { return isEnabled(action, OperationName.Query) ; }
         @Override protected boolean allowUpdate(HttpAction action)   { return isEnabled(action, OperationName.Update) ; }
-        @Override protected boolean allowREST_R(HttpAction action)   { return isEnabled(action, OperationName.GSP_R) || isEnabled(action, OperationName.GSP) ; }
-        @Override protected boolean allowREST_W(HttpAction action)   { return isEnabled(action, OperationName.GSP) ; }
-        // Quad operations tied to presence/absence of GSP.
-        @Override protected boolean allowQuadsR(HttpAction action)   { return isEnabled(action, OperationName.GSP_R) || isEnabled(action, OperationName.GSP) ; }
-        @Override protected boolean allowQuadsW(HttpAction action)   { return isEnabled(action, OperationName.GSP) ; }
+        @Override protected boolean allowREST_R(HttpAction action)   { return isEnabled(action, OperationName.GSP_R) || isEnabled(action, OperationName.GSP_RW) ; }
+        @Override protected boolean allowREST_W(HttpAction action)   { return isEnabled(action, OperationName.GSP_RW) ; }
+        @Override protected boolean allowQuadsR(HttpAction action)   { return isEnabled(action, OperationName.Quads_R) || isEnabled(action, OperationName.Quads_RW) ; }
+        @Override protected boolean allowQuadsW(HttpAction action)   { return isEnabled(action, OperationName.Quads_RW) ; }
 
         // Test whether there is a configuration that allows this action as the operation given.
         // Ignores the operation in the action (set due to parsing - it might be "quads"
@@ -109,7 +110,7 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL
     private final ActionSPARQL uploadServlet   = new SPARQL_Upload() ;
     private final ActionSPARQL gspServlet_R    = new SPARQL_GSP_R() ;
     private final ActionSPARQL gspServlet_RW   = new SPARQL_GSP_RW() ;
-    private final ActionSPARQL restQuads_R     = new REST_Quads_R() ;
+    private final ActionSPARQL restQuads_R     = new REST_Quads_R() ; // XXX
     private final ActionSPARQL restQuads_RW    = new REST_Quads_RW() ;
     
     public SPARQL_UberServlet() { super(); }
@@ -151,6 +152,7 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL
         String method = request.getMethod() ;
         
         DataAccessPoint desc = action.getDataAccessPoint() ;
+        System.err.println("**** SET READONLY ****") ;
         DataService dSrv = action.getDataService() ;
 
 //        if ( ! dSrv.isActive() )
@@ -184,15 +186,6 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL
         boolean hasTrailing = ( trailing.length() != 0 ) ;
         
         if ( !hasTrailing && !hasParams ) {
-            // REST quads operations.
-//            if ( serviceDispatch(action, OperationName.GSP_R, restQuads_R) ) return ;
-//            if ( serviceDispatch(action, OperationName.GSP, restQuads_RW) ) return ;
-            
-//            boolean isPOST = method.equals(HttpNames.METHOD_POST) ;
-//            if ( isPOST ) {
-//                // Differentiate SPARQL query, SPARQL update by content type.
-//            }
-            
             // REST dataset.
             boolean isGET = method.equals(HttpNames.METHOD_GET) ;
             boolean isHEAD = method.equals(HttpNames.METHOD_HEAD) ;
@@ -202,13 +195,16 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL
                 if ( allowREST_R(action) )
                     restQuads_R.executeLifecycle(action) ;
                 else
-                    ServletOps.errorForbidden("Forbidden: "+method+" on dataset") ;
+                    ServletOps.errorMethodNotAllowed("Read-only dataset : "+method) ;
                 return ; 
             }
+            // If the read-only server has the same name as the writable server, 
+            // and the default fro a read-only server is "/data", like a writable dataset,
+            // this test is insufficient.
             if ( allowREST_W(action) )
                 restQuads_RW.executeLifecycle(action) ;
             else
-                ServletOps.errorForbidden("Forbidden: "+method+" on dataset") ;
+                ServletOps.errorMethodNotAllowed("Read-only dataset : "+method) ;
             return ;
         }
         
@@ -221,7 +217,7 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL
             if ( hasParamQuery || ( isPOST && contentTypeSPARQLQuery.equalsIgnoreCase(ct) ) ) {
                 // SPARQL Query
                 if ( !allowQuery(action) )
-                    ServletOps.errorForbidden("Forbidden: SPARQL query") ;
+                    ServletOps.errorMethodNotAllowed("SPARQL query : "+method) ;
                 executeRequest(action, queryServlet) ;
                 return ;
             }
@@ -231,7 +227,7 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL
             if ( isPOST && ( hasParamUpdate || contentTypeSPARQLUpdate.equalsIgnoreCase(ct) ) ) {
                 // SPARQL Update
                 if ( !allowUpdate(action) )
-                    ServletOps.errorForbidden("Forbidden: SPARQL update") ;
+                    ServletOps.errorMethodNotAllowed("SPARQL update : "+method) ; 
                 executeRequest(action, updateServlet) ;
                 return ;
             }
@@ -243,7 +239,7 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL
             }
             
             ServletOps.errorBadRequest("Malformed request") ;
-            ServletOps.errorForbidden("Forbidden: SPARQL Graph Store Protocol : Read operation : "+method) ;
+            ServletOps.errorMethodNotAllowed("SPARQL Graph Store Protocol : "+method) ;
         }
         
         final boolean checkForPossibleService = true ;
@@ -256,11 +252,11 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL
             if ( serviceDispatch(action, OperationName.Upload, uploadServlet) ) return ;
             if ( hasParams ) {
                 if ( serviceDispatch(action, OperationName.GSP_R, gspServlet_R) ) return ; 
-                if ( serviceDispatch(action, OperationName.GSP, gspServlet_RW) ) return ;
+                if ( serviceDispatch(action, OperationName.GSP_RW, gspServlet_RW) ) return ;
             } else {
                 // No parameters - do as a quads operation on the dataset.
                 if ( serviceDispatch(action, OperationName.GSP_R, restQuads_R) ) return ;
-                if ( serviceDispatch(action, OperationName.GSP, restQuads_RW) ) return ;
+                if ( serviceDispatch(action, OperationName.GSP_RW, restQuads_RW) ) return ;
             }
         }
         // There is a trailing part - params are illegal by this point.
@@ -331,18 +327,21 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL
     }
 
     private void executeRequest(HttpAction action, ActionSPARQL servlet) {
-        servlet.executeLifecycle(action) ;
-        // A call to "doCommon" or a forwarded dispatch looses "action".
-//      try
-//      {
-//          String target = getEPName(desc.name, endpointList) ;
-//          if ( target == null )
-//              errorMethodNotAllowed(request.getMethod()) ;
-//          // ** relative servlet forward
-//          request.getRequestDispatcher(target).forward(request, response) ;    
-//          // ** absolute srvlet forward
-//          // getServletContext().getRequestDispatcher(target) ;
-//      } catch (Exception e) { errorOccurred(e) ; }        
+        if ( true ) {
+            // Execute an ActionSPARQL.
+            // Bypasses HttpServlet.service to doMethod dispatch.
+            servlet.executeLifecycle(action) ;
+            return ;
+        }
+        if ( false )  {
+            // Execute by calling the whole servlet mechanism.
+            // This causes HttpServlet.service to call the appropriate doMethod. 
+            // but the action, and the id, are not passed on and a ne one is created.
+            try { servlet.service(action.request, action.response) ; }
+            catch (ServletException | IOException e) {
+                ServletOps.errorOccurred(e); 
+            }
+        }
     }
 
     protected static MediaType contentNegotationQuads(HttpAction action) {

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
----------------------------------------------------------------------
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 277bf47..b250250 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
@@ -154,6 +154,8 @@ public class ServletOps {
     }
 
     public static String formatForLog(String string) {
+        if ( string == null )
+            return "<null>" ;
         string = string.replace('\n', ' ') ;
         string = string.replace('\r', ' ') ;
         return string ;

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java
index 8806968..b8f6e9e 100644
--- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java
+++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java
@@ -33,6 +33,7 @@ import org.junit.runners.Suite ;
 @Suite.SuiteClasses( {
     TestHttpOp.class
     , TestSPARQLProtocol.class
+    , TestHttpOptions.class
     , TestDatasetGraphAccessorHTTP.class
     , TestDatasetAccessorHTTP.class
     , TestQuery.class
@@ -40,6 +41,7 @@ import org.junit.runners.Suite ;
     , TestDatasetOps.class
     , TestFileUpload.class
     , TestAdmin.class
+    , TestServerReadOnly.class
 })
 
 public class TS_Fuseki extends ServerTest

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestHttpOptions.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestHttpOptions.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestHttpOptions.java
new file mode 100644
index 0000000..8f26d1a
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestHttpOptions.java
@@ -0,0 +1,61 @@
+/**
+ * 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;
+
+import static org.apache.jena.fuseki.ServerTest.serviceGSP ;
+import static org.apache.jena.fuseki.ServerTest.serviceQuery ;
+import static org.apache.jena.fuseki.ServerTest.serviceUpdate ;
+import static org.apache.jena.fuseki.ServerTest.urlDataset ;
+
+import org.junit.Test ;
+
+public class TestHttpOptions extends AbstractFusekiTest
+{
+    @Test
+    public void options_query() {
+        String v = FusekiTest.execOptions(serviceQuery) ;
+        FusekiTest.assertStringList(v, "GET", "OPTIONS", "POST") ;
+    }
+    
+    @Test
+    public void options_update() {
+        String v = FusekiTest.execOptions(serviceUpdate) ;
+        FusekiTest.assertStringList(v, "OPTIONS", "POST") ;
+    }
+    
+    @Test
+    public void options_dataset_01() {
+        String v = FusekiTest.execOptions(urlDataset) ;
+        // Not DELETE
+        FusekiTest.assertStringList(v, "HEAD", "GET", "OPTIONS", "POST", "PUT") ;
+    }
+
+    @Test
+    public void options_dataset_02() {
+        //"quads"
+    }
+    
+    @Test
+    public void options_gsp_rw() {
+        String v = FusekiTest.execOptions(serviceGSP+"?default") ;
+        FusekiTest.assertStringList(v, "GET", "OPTIONS", "HEAD", "POST", "PUT", "DELETE") ;
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/d61d1ca2/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestServerReadOnly.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestServerReadOnly.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestServerReadOnly.java
new file mode 100644
index 0000000..8e3f98a
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestServerReadOnly.java
@@ -0,0 +1,134 @@
+/**
+ * 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;
+
+import static org.apache.jena.fuseki.ServerTest.serviceGSP ;
+import static org.apache.jena.fuseki.ServerTest.serviceQuery ;
+import static org.apache.jena.fuseki.ServerTest.serviceUpdate ;
+
+import java.nio.charset.StandardCharsets ;
+
+import org.apache.http.HttpEntity ;
+import org.apache.http.entity.StringEntity ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.query.Query ;
+import org.apache.jena.query.QueryExecution ;
+import org.apache.jena.query.QueryExecutionFactory ;
+import org.apache.jena.query.QueryFactory ;
+import org.apache.jena.riot.web.HttpOp ;
+import org.apache.jena.update.UpdateExecutionFactory ;
+import org.apache.jena.update.UpdateFactory ;
+import org.apache.jena.update.UpdateProcessor ;
+import org.apache.jena.update.UpdateRequest ;
+import org.apache.jena.web.HttpSC ;
+import org.junit.AfterClass ;
+import org.junit.BeforeClass ;
+import org.junit.Test ;
+
+/** Tests on a read only server. */
+public class TestServerReadOnly extends BaseTest
+{
+    // readonly server.
+    @BeforeClass
+    public static void allocServerForSuite() {
+        ServerTest.allocServer(false) ;
+    }
+
+    @AfterClass
+    public static void freeServerForSuite() {
+        ServerTest.freeServer() ;
+    }
+    
+    @Test
+    public void query_readonly() {
+        Query query = QueryFactory.create("ASK{}");
+        QueryExecution qexec = QueryExecutionFactory.sparqlService(serviceQuery, query);
+        qexec.execAsk() ;
+    }
+    
+    @Test()
+    public void update_readonly() {
+        FusekiTest.exec404( () -> {
+            UpdateRequest update = UpdateFactory.create("INSERT DATA {}");
+            UpdateProcessor proc = UpdateExecutionFactory.createRemote(update, serviceUpdate);
+            proc.execute();
+        });
+    }
+
+    
+    @Test
+    public void gsp_w_readonly_POST() {
+        // Try to write
+        FusekiTest.execWithHttpException(HttpSC.METHOD_NOT_ALLOWED_405, ()->{
+            HttpEntity e = new StringEntity("", StandardCharsets.UTF_8) ;
+            HttpOp.execHttpPost(serviceGSP+"?default", e);
+        }) ;
+    }
+    
+    @Test
+    public void gsp_w_readonly_PUT() {
+        // Try to write
+        FusekiTest.execWithHttpException(HttpSC.METHOD_NOT_ALLOWED_405, ()->{
+            HttpEntity e = new StringEntity("", StandardCharsets.UTF_8) ;
+            HttpOp.execHttpPut(serviceGSP+"?default", e);
+        }) ;
+    }
+
+    @Test
+    public void gsp_w_readonly_DELETE() {
+        // Try to write
+        FusekiTest.execWithHttpException(HttpSC.METHOD_NOT_ALLOWED_405, ()->{
+            HttpOp.execHttpDelete(serviceGSP+"?default");
+        }) ;
+    }
+    
+    @Test
+    public void dataset_w_readonly_POST() {
+        // Try to write
+        FusekiTest.execWithHttpException(HttpSC.METHOD_NOT_ALLOWED_405, ()->{
+            HttpEntity e = new StringEntity("", StandardCharsets.UTF_8) ;
+            HttpOp.execHttpPost(ServerTest.urlDataset, e) ;
+        }) ;
+    }
+
+    @Test
+    public void dataset_w_readonly_PUT() {
+        // Try to write
+        FusekiTest.execWithHttpException(HttpSC.METHOD_NOT_ALLOWED_405, ()->{
+            HttpEntity e = new StringEntity("", StandardCharsets.UTF_8) ;
+            HttpOp.execHttpPut(ServerTest.urlDataset, e) ;
+        }) ;
+    }
+
+    @Test
+    public void dataset_w_readonly_DELETE() {
+        // Try to write
+        FusekiTest.execWithHttpException(HttpSC.METHOD_NOT_ALLOWED_405, ()->{
+            HttpOp.execHttpDelete(ServerTest.urlDataset) ;
+        }) ;
+    }
+
+    @Test
+    public void options_gsp_readonly() {
+        String v = FusekiTest.execOptions(serviceGSP+"?default") ;
+        FusekiTest.assertStringList(v, "GET", "OPTIONS", "HEAD") ;
+    }
+
+}
+