Merge pull request #649 from afs/fuseki-1794

JENA-1794: Look for rdf:type fuseki:Service if duplicate fuseki:name
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
index 1225718..3d5e3b8 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
@@ -27,11 +27,7 @@
 import java.io.StringReader;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
+import java.util.*;
 
 import javax.servlet.http.HttpServletRequest;
 
@@ -66,11 +62,7 @@
 import org.apache.jena.query.Dataset;
 import org.apache.jena.query.ReadWrite;
 import org.apache.jena.rdf.model.*;
-import org.apache.jena.riot.Lang;
-import org.apache.jena.riot.RDFDataMgr;
-import org.apache.jena.riot.RDFLanguages;
-import org.apache.jena.riot.RDFParser;
-import org.apache.jena.riot.WebContent;
+import org.apache.jena.riot.*;
 import org.apache.jena.riot.system.StreamRDF;
 import org.apache.jena.riot.system.StreamRDFLib;
 import org.apache.jena.shared.uuid.JenaUUID;
@@ -82,6 +74,7 @@
 import org.apache.jena.update.UpdateAction;
 import org.apache.jena.update.UpdateFactory;
 import org.apache.jena.update.UpdateRequest;
+import org.apache.jena.vocabulary.RDF;
 import org.apache.jena.web.HttpSC;
 
 public class ActionDatasets extends ActionContainerItem {
@@ -169,21 +162,10 @@
                 RDFDataMgr.write(outCopy, model, Lang.TURTLE);
             }
             // ----
-
             // Process configuration.
-            Statement stmt = getOne(model, null, pServiceName, null);
-            if ( stmt == null ) {
-                StmtIterator sIter = model.listStatements(null, pServiceName, (RDFNode)null );
-                if ( ! sIter.hasNext() )
-                    ServletOps.errorBadRequest("No name given in description of Fuseki service");
-                sIter.next();
-                if ( sIter.hasNext() )
-                    ServletOps.errorBadRequest("Multiple names given in description of Fuseki service");
-                throw new InternalErrorException("Inconsistent: getOne didn't fail the second time");
-            }
 
-            if ( ! stmt.getObject().isLiteral() )
-                ServletOps.errorBadRequest("Found "+FmtUtils.stringForRDFNode(stmt.getObject())+" : Service names are strings, then used to build the external URI");
+            // Returns the "service fu:name NAME" statement
+            Statement stmt = findService(model);
 
             Resource subject = stmt.getSubject();
             Literal object = stmt.getObject().asLiteral();
@@ -255,6 +237,45 @@
         return null;
     }
 
+    /** Find the service resource. There must be only one in the configuration. */
+    private Statement findService(Model model) {
+        // Try to find by unique pServiceName (max backwards compatibility)
+        // then try to find by rdf:type fuseki:Service.
+
+        // JENA-1794
+        Statement stmt = getOne(model, null, pServiceName, null);
+        // null means 0 or many, not one.
+
+        if ( stmt == null ) {
+            // This calculates { ?x rdf:type fu:Service ; ?x fu:name ?name }
+            // One and only one service.
+            Statement stmt2 = getOne(model, null, RDF.type, FusekiVocab.fusekiService);
+            if ( stmt2 == null ) {
+                int count = model.listStatements(null, RDF.type, FusekiVocab.fusekiService).toList().size();
+                if ( count == 0 )
+                    ServletOps.errorBadRequest("No triple rdf:type fuseki:Service found");
+                else
+                    ServletOps.errorBadRequest("Multiple Fuseki service descriptions");
+            }
+            Statement stmt3 = getOne(model, stmt2.getSubject(), pServiceName, null);
+            if ( stmt3 == null ) {
+                StmtIterator sIter = model.listStatements(stmt2.getSubject(), pServiceName, (RDFNode)null );
+                if ( ! sIter.hasNext() )
+                    ServletOps.errorBadRequest("No name given in description of Fuseki service");
+                sIter.next();
+                if ( sIter.hasNext() )
+                    ServletOps.errorBadRequest("Multiple names given in description of Fuseki service");
+                throw new InternalErrorException("Inconsistent: getOne didn't fail the second time");
+            }
+            stmt = stmt3;
+        }
+
+        if ( ! stmt.getObject().isLiteral() )
+            ServletOps.errorBadRequest("Found "+FmtUtils.stringForRDFNode(stmt.getObject())+" : Service names are strings, then used to build the external URI");
+
+        return stmt;
+    }
+
     @Override
     protected JsonValue execPostItem(HttpAction action) {
         String name = getItemDatasetName(action);
@@ -312,7 +333,7 @@
         String name = getItemDatasetName(action);
         if ( name == null )
             name = "";
-        action.log.info(format("[%d] DELETE ds=%s", action.id, name));
+        action.log.info(format("[%d] DELETE dataset=%s", action.id, name));
 
         if ( ! action.getDataAccessPointRegistry().isRegistered(name) )
             ServletOps.errorNotFound("No such dataset registered: "+name);
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/test/java/org/apache/jena/fuseki/TestAdmin.java b/jena-fuseki2/jena-fuseki-webapp/src/test/java/org/apache/jena/fuseki/TestAdmin.java
index e29f90d..7516fa6 100644
--- a/jena-fuseki2/jena-fuseki-webapp/src/test/java/org/apache/jena/fuseki/TestAdmin.java
+++ b/jena-fuseki2/jena-fuseki-webapp/src/test/java/org/apache/jena/fuseki/TestAdmin.java
@@ -55,7 +55,7 @@
 public class TestAdmin extends AbstractFusekiTest {
 
     // Name of the dataset in the assembler file.
-    static String dsTest    = "test-ds2";
+    static String dsTest    = "test-ds1";
     static String dsTestInf = "test-ds4";
     static String fileBase  = "testing/";
 
@@ -133,7 +133,7 @@
     @Test public void add_delete_dataset_2() {
         checkNotThere(dsTest);
 
-        File f = new File(fileBase+"config-ds-plain.ttl");
+        File f = new File(fileBase+"config-ds-plain-1.ttl");
         {
             org.apache.http.entity.ContentType ct = org.apache.http.entity.ContentType.parse(WebContent.contentTypeTurtle+"; charset="+WebContent.charsetUTF8);
             HttpEntity e = new FileEntity(f, ct);
@@ -153,7 +153,7 @@
         deleteDataset(dsTest);
     }
 
-    @Test public void add_delete_dataset_3() throws Exception {
+    @Test public void add_delete_dataset_3() {
         checkNotThere(dsTest);
         addTestDataset();
         checkExists(dsTest);
@@ -164,7 +164,7 @@
         deleteDataset(dsTest);
     }
 
-    @Test public void add_delete_dataset_4() throws Exception {
+    @Test public void add_delete_dataset_4() {
         checkNotThere(dsTest);
         checkNotThere(dsTestInf);
         addTestDatasetInf();
@@ -178,6 +178,12 @@
         deleteDataset(dsTestInf);
     }
 
+    @Test public void add_delete_dataset_5() {
+        // New style operations : cause two fuseki:names
+        addTestDataset(fileBase+"config-ds-plain-2.ttl");
+        checkExists("test-ds2");
+    }
+
     @Test public void add_error_1() {
         FusekiTest.execWithHttpException(HttpSC.BAD_REQUEST_400,
                                          ()-> addTestDataset(fileBase+"config-ds-bad-name-1.ttl"));
@@ -354,7 +360,7 @@
     // -- Add
 
     private static void addTestDataset() {
-        addTestDataset(fileBase+"config-ds-plain.ttl");
+        addTestDataset(fileBase+"config-ds-plain-1.ttl");
     }
 
     private static void addTestDatasetInf() {
diff --git a/jena-fuseki2/jena-fuseki-webapp/testing/config-ds-plain.ttl b/jena-fuseki2/jena-fuseki-webapp/testing/config-ds-plain-1.ttl
similarity index 72%
rename from jena-fuseki2/jena-fuseki-webapp/testing/config-ds-plain.ttl
rename to jena-fuseki2/jena-fuseki-webapp/testing/config-ds-plain-1.ttl
index aba5d82..f993f35 100644
--- a/jena-fuseki2/jena-fuseki-webapp/testing/config-ds-plain.ttl
+++ b/jena-fuseki2/jena-fuseki-webapp/testing/config-ds-plain-1.ttl
@@ -1,3 +1,5 @@
+## 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#>
@@ -5,9 +7,9 @@
 PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
 PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
 
-<#service1> rdf:type fuseki:Service ;
+<#service1> #rdf:type fuseki:Service ;
     # URI of the dataset -- http://host:port/ds
-    fuseki:name                        "test-ds2" ; 
+    fuseki:name                        "test-ds1" ; 
     fuseki:serviceQuery                "sparql" ;
     fuseki:dataset                     <#emptyDataset> ;
     .
diff --git a/jena-fuseki2/jena-fuseki-webapp/testing/config-ds-plain-2.ttl b/jena-fuseki2/jena-fuseki-webapp/testing/config-ds-plain-2.ttl
new file mode 100644
index 0000000..d4b3fc9
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/testing/config-ds-plain-2.ttl
@@ -0,0 +1,18 @@
+## 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#>
+
+<#service1> rdf:type fuseki:Service ;
+    fuseki:name                       "test-ds2" ; 
+    fuseki:endpoint [
+        fuseki:operation fuseki:query ; 
+        fuseki:name "sparql"
+    ] ;
+    fuseki:dataset                    <#emptyDataset> ;
+    .
+
+<#emptyDataset> rdf:type ja:MemoryDataset .