JENA-1795: HEAD for GSP operations
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/GSP_R.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/GSP_R.java
index efef1f3..606ab0f 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/GSP_R.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/GSP_R.java
@@ -33,9 +33,9 @@
 import org.apache.jena.sparql.core.DatasetGraph;
 
 public class GSP_R extends GSP_Base {
-    
+
     public GSP_R() {}
-    
+
     @Override
     protected void doGet(HttpAction action) {
         if ( isQuads(action) )
@@ -45,6 +45,7 @@
     }
 
     protected void execGetQuads(HttpAction action) {
+        ActionLib.setCommonHeaders(action.response);
         MediaType mediaType = ActionLib.contentNegotationQuads(action);
         ServletOutputStream output;
         try { output = action.response.getOutputStream(); }
@@ -85,6 +86,7 @@
     }
 
     protected void execGetGSP(HttpAction action) {
+        ActionLib.setCommonHeaders(action.response);
         MediaType mediaType = ActionLib.contentNegotationRDF(action);
 
         ServletOutputStream output;
@@ -98,7 +100,6 @@
         if ( action.verbose )
             action.log.info(format("[%d]   Get: Content-Type=%s, Charset=%s => %s",
                             action.id, mediaType.getContentType(), mediaType.getCharset(), lang.getName()));
-        ActionLib.setCommonHeaders(action.response);
         try {
             DatasetGraph dsg = decideDataset(action);
             GSPTarget target = determineTarget(dsg, action);
@@ -141,23 +142,37 @@
 
     @Override
     protected void doHead(HttpAction action) {
-        action.beginRead();
+        if ( isQuads(action) )
+            execHeadQuads(action);
+        else
+            execHeadGSP(action);
+    }
+
+    protected void execHeadQuads(HttpAction action) {
         ActionLib.setCommonHeaders(action.response);
+        MediaType mediaType = ActionLib.contentNegotationQuads(action);
+        if ( action.verbose )
+            action.log.info(format("[%d]   Head: Content-Type=%s", action.id, mediaType.getContentType()));
+        ServletOps.success(action);
+    }
+
+    protected void execHeadGSP(HttpAction action) {
+        ActionLib.setCommonHeaders(action.response);
+        MediaType mediaType = ActionLib.contentNegotationRDF(action);
+        if ( action.verbose )
+            action.log.info(format("[%d]   Head: Content-Type=%s", action.id, mediaType.getContentType()));
+        // Check graph not 404.
+        action.beginRead();
         try {
             DatasetGraph dsg = decideDataset(action);
             GSPTarget target = determineTarget(dsg, action);
             if ( action.log.isDebugEnabled() )
-                action.log.debug("HEAD->" + target);
-            if ( !target.exists() ) {
-                ServletOps.successNotFound(action);
-                return;
-            }
-            MediaType mediaType = ActionLib.contentNegotationRDF(action);
+                action.log.debug("HEAD->"+target);
+            boolean exists = target.exists();
+            if ( ! exists )
+                ServletOps.errorNotFound("No such graph: <"+target.name+">");
             ServletOps.success(action);
-        }
-        finally {
-            action.endRead();
-        }
+        } finally { action.endRead(); }
     }
 
     @Override
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
new file mode 100644
index 0000000..cceb07d
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHTTP.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.main;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.jena.atlas.lib.StrUtils;
+import org.apache.jena.atlas.web.WebLib;
+import org.apache.jena.graph.Graph;
+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.Lang;
+import org.apache.jena.riot.web.HttpNames;
+import org.apache.jena.riot.web.HttpOp;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphFactory;
+import org.apache.jena.sparql.sse.SSE;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Test HTTP level details */
+public class TestHTTP {
+    private static FusekiServer server = null;
+    private static int port;
+
+    private static Model data;
+    private static Dataset dataset;
+
+    private static String URL;
+
+    @BeforeClass
+    public static void beforeClass() {
+        port = WebLib.choosePort();
+        URL = "http://localhost:" + port + "/ds";
+        Graph graph = SSE.parseGraph(StrUtils.strjoinNL
+            ("(graph"
+            ,"   (:s :p 1)"
+            ,")"));
+        data = ModelFactory.createModelForGraph(graph);
+
+        DatasetGraph dsgData = DatasetGraphFactory.create();
+        dsgData.add(SSE.parseQuad("(:g :s :p 2 )"));
+        dataset = DatasetFactory.wrap(dsgData);
+
+        DatasetGraph dsg = DatasetGraphFactory.createTxnMem();
+
+        FusekiServer server = FusekiServer.create()
+            .add("/ds", dsg)
+            .port(port)
+            .build();
+        server.start();
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        if ( server != null )
+            server.stop();
+    }
+
+    // GET
+
+    @Test public void gspGet_dataset_1() {
+        // Base URL, default content type => N-Quads (dump format)
+        HttpOp.execHttpGet(URL, null, (base, response)->{
+            String h = response.getFirstHeader(HttpNames.hContentType).getValue();
+            assertNotNull(h);
+            assertEquals(Lang.NQUADS.getHeaderString(), h);
+        });
+    }
+
+
+    @Test public void gspGet_dataset_2() {
+        String ct = Lang.TRIG.getHeaderString();
+        HttpOp.execHttpGet(URL, ct, (base, response)->{
+            String h = response.getFirstHeader(HttpNames.hContentType).getValue();
+            assertNotNull(h);
+            assertEquals(ct, h);
+        });
+    }
+
+    @Test public void gspGet_graph_1() {
+        String target = URL+"?default";
+        HttpOp.execHttpGet(target, null, (base, response)->{
+            String h = response.getFirstHeader(HttpNames.hContentType).getValue();
+            assertNotNull(h);
+            // "Traditional default".
+            assertEquals(Lang.RDFXML.getHeaderString(), h);
+        });
+    }
+
+    @Test public void gspGet_graph_2() {
+        String target = URL+"?default";
+        String ct = Lang.TTL.getHeaderString();
+        HttpOp.execHttpGet(target, ct, (base, response)->{
+            String h = response.getFirstHeader(HttpNames.hContentType).getValue();
+            assertNotNull(h);
+            assertEquals(ct, h);
+        });
+    }
+
+    // HEAD
+
+    @Test public void gspHead_dataset_1() {
+        // Base URL, default content type => N-Quads (dump format)
+        HttpOp.execHttpHead(URL, null, (base, response)->{
+            String h = response.getFirstHeader(HttpNames.hContentType).getValue();
+            assertNotNull(h);
+            assertEquals(Lang.NQUADS.getHeaderString(), h);
+        });
+    }
+
+
+    @Test public void gspHead_dataset_2() {
+        String ct = Lang.TRIG.getHeaderString();
+        HttpOp.execHttpHead(URL, ct, (base, response)->{
+            String h = response.getFirstHeader(HttpNames.hContentType).getValue();
+            assertNotNull(h);
+            assertEquals(ct, h);
+        });
+    }
+
+    @Test public void gspHead_graph_1() {
+        String target = URL+"?default";
+        HttpOp.execHttpHead(target, null, (base, response)->{
+            String h = response.getFirstHeader(HttpNames.hContentType).getValue();
+            assertNotNull(h);
+            // "Traditional default".
+            assertEquals(Lang.RDFXML.getHeaderString(), h);
+        });
+    }
+
+    @Test public void gspHead_graph_2() {
+        String target = URL+"?default";
+        String ct = Lang.TTL.getHeaderString();
+        HttpOp.execHttpHead(target, ct, (base, response)->{
+            String h = response.getFirstHeader(HttpNames.hContentType).getValue();
+            assertNotNull(h);
+            assertEquals(ct, h);
+        });
+    }
+}