Repository: apache/jena (java)

Issue:
The Jena build fails on a machine with only an IPv6 address
### Change

This is a test code failure only.

`TestModShiro.access_localhost` attempts to discover the machine's non-localhost address in order to test that a requirement of localhost is enforced.

The URL is not correctly formed for an IPv6 address. It seems that machines with both IPv4 and IPv6 networking tend to yield the IPv4 address.



### Are you interested in contributing a pull request for this task?

Yes

Pull request description:
GH-2963: Cope with IPv6 address for the local machine
GitHub issue resolved #2963

An accumulation of things during the build cycle:

Pull request Description:
* fix for #2963
* Test rename because two tests classes with the same name is confusing
* Improve the output of the IRI validator 
* Better error messages for SPARQL protocol errors.
* Add a script to the Fuseki download to run Fuseki plain (missed in 5.3.0)

----

 - [x] Commits have been squashed to remove intermediate development commit messages.
 - [x] Key commit messages start with the issue number (GH-xxxx)

By submitting this pull request, I acknowledge that I am making a contribution to the Apache Software Foundation under the terms and conditions of the [Contributor's Agreement](https://www.apache.org/licenses/contributor-agreements.html).


Patch:
diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml
index ee63f2654c..0c328368aa 100644
--- a/.github/workflows/ui.yml
+++ b/.github/workflows/ui.yml
@@ -17,7 +17,7 @@ jobs:
     if: github.repository == 'apache/jena'
 
     runs-on: ${{ matrix.os }}
-    timeout-minutes: 5
+    timeout-minutes: 15
 
     strategy:
       fail-fast: false
@@ -42,7 +42,7 @@ jobs:
           java-version: ${{ matrix.java_version }}
 
       - name: Build with Maven
-        run: mvn -B --file pom.xml --projects jena-fuseki2/jena-fuseki-ui --also-make-dependents test install
+        run: mvn -B --file pom.xml clean install -Pdev -Dmaven.javadoc.skip=true
 
       - name: Lint
         working-directory: jena-fuseki2/jena-fuseki-ui
@@ -54,6 +54,7 @@ jobs:
 
     runs-on: ${{ matrix.os }}
     timeout-minutes: 15
+    needs: unit-test-and-build
 
     strategy:
       fail-fast: false
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/net/Host.java b/jena-base/src/main/java/org/apache/jena/atlas/net/Host.java
index 12449dbd71..30914d0ca4 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/net/Host.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/net/Host.java
@@ -19,6 +19,7 @@
 package org.apache.jena.atlas.net;
 
 import java.io.IOException;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.UnknownHostException;
@@ -29,16 +30,24 @@ import org.apache.jena.atlas.io.IOX;
 
 public class Host {
     /**
-     * Returns a host address, for what is most likely the machine's LAN IP address
-     * (not a loopback address, 127.0.0.x or ::1). If there is no other choice, it returns
-     * <code>a local host address</code>.
+     * Returns a host address in a form suitable for an IRI authority host.
+     * This is not a loopback address, 127.0.0.x or ::1.
      */
-    public static String getHostAddress() {
+    public static String getHostAddressForIRI() {
         try {
             InetAddress addr = getLocalHostLANAddress$();
             if ( addr == null )
                 return null;
-            return addr.getHostAddress();
+            String hostAddress = addr.getHostAddress();
+            if ( addr instanceof Inet6Address ) {
+                // Remove zoneId
+                hostAddress = hostAddress.replaceFirst("%.*","");
+                // If you do want the zone id, the % needs encoding.
+                // hostAddress = hostAddress.replaceFirst("%","%25");
+                // Wrap in "[ ... ]"
+                hostAddress = "["+hostAddress+"]";
+            }
+            return hostAddress;
         } catch ( UnknownHostException ex) {
             return null;
         }
diff --git a/jena-core/src/main/java/org/apache/jena/graph/langtag/LangTags.java b/jena-core/src/main/java/org/apache/jena/graph/langtag/LangTags.java
index 29efa034b0..bf28bdd9ee 100644
--- a/jena-core/src/main/java/org/apache/jena/graph/langtag/LangTags.java
+++ b/jena-core/src/main/java/org/apache/jena/graph/langtag/LangTags.java
@@ -113,7 +113,7 @@ public class LangTags {
                 sb.setLength(0);
                 continue;
             }
-            error("Bad character: (0x%02X) '%c' index %d", (int)ch, str(ch), idx);
+            error("Bad character: (0x%02X) '%s' index %d", (int)ch, str(ch), idx);
         }
         String strLast = sb.toString();
         if ( strLast.isEmpty() ) {
diff --git a/jena-core/src/main/java/org/apache/jena/iri3986/provider/JenaSeveritySettings.java b/jena-core/src/main/java/org/apache/jena/iri3986/provider/JenaSeveritySettings.java
index 3baa20f5b7..f185e1dd0b 100644
--- a/jena-core/src/main/java/org/apache/jena/iri3986/provider/JenaSeveritySettings.java
+++ b/jena-core/src/main/java/org/apache/jena/iri3986/provider/JenaSeveritySettings.java
@@ -78,12 +78,12 @@ public class JenaSeveritySettings {
         SeverityMap.setSeverity(severityMap, Issue.file_bad_form,                     Severity.WARNING);
         SeverityMap.setSeverity(severityMap, Issue.file_relative_path,                Severity.WARNING);
 
-        // did
+        // DID
         SeverityMap.setSeverity(severityMap, Issue.did_bad_syntax,                    Severity.ERROR);
+
         // OID
         SeverityMap.setSeverity(severityMap, Issue.oid_bad_syntax,                    Severity.ERROR);
-        // OID
-        SeverityMap.setSeverity(severityMap, Issue.oid_scheme_not_registered,                Severity.WARNING);
+        SeverityMap.setSeverity(severityMap, Issue.oid_scheme_not_registered,         Severity.WARNING);
 
         return SeverityMap.create("Jena settings", severityMap);
     }
diff --git a/jena-fuseki2/apache-jena-fuseki/fuseki-plain b/jena-fuseki2/apache-jena-fuseki/fuseki-plain
new file mode 100755
index 0000000000..c90d69fb1e
--- /dev/null
+++ b/jena-fuseki2/apache-jena-fuseki/fuseki-plain
@@ -0,0 +1,21 @@
+#!/bin/sh
+# 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.
+
+## Run Fuseki as a standalone server with no UI
+
+export MAIN=org.apache.jena.fuseki.main.cmds.FusekiMainCmd
+exec fuseki-server "$#"
diff --git a/jena-fuseki2/apache-jena-fuseki/fuseki-server b/jena-fuseki2/apache-jena-fuseki/fuseki-server
index 7c34e578ad..3344a71f79 100755
--- a/jena-fuseki2/apache-jena-fuseki/fuseki-server
+++ b/jena-fuseki2/apache-jena-fuseki/fuseki-server
@@ -115,8 +115,10 @@ then
 fi
 
 ## Plain server, no UI, no admin work area.
-## MAIN=org.apache.jena.fuseki.main.cmds.FusekiMainCmd
-MAIN=org.apache.jena.fuseki.main.cmds.FusekiServerCmd
+## MAIN="${MAIN:-org.apache.jena.fuseki.main.cmds.FusekiMainCmd}"
+
+## Fuseki server, with UI and admin area.
+MAIN="${MAIN:-org.apache.jena.fuseki.main.cmds.FusekiServerCmd}"
 
 if [ -n "$LOGGING" ]
 then
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 6d527f18ee..84f4d9fd1b 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
@@ -237,6 +237,11 @@ public abstract class SPARQLQueryProcessor extends ActionService
     }
 
     protected void execute(String queryString, HttpAction action) {
+        if ( queryString.isEmpty() )
+            ServletOps.errorBadRequest("Error: Empty query string");
+        if ( queryString.isBlank() )
+            ServletOps.errorBadRequest("Error: Blank query string");
+
         String queryStringLog = ServletOps.formatForLog(queryString);
         if ( action.verbose ) {
             String str = queryString;
@@ -266,7 +271,7 @@ public abstract class SPARQLQueryProcessor extends ActionService
             ServletOps.errorBadRequest("Error: \n" + queryString + "\n" + msg);
         }
 
-        // Assumes finished whole thing by end of sendResult.
+        // Assumes finished whole thing by end of sendResults.
         try {
             action.beginRead();
             Pair<DatasetGraph, Query> p = decideDataset(action, query, queryStringLog);
@@ -277,7 +282,7 @@ public abstract class SPARQLQueryProcessor extends ActionService
 
             try ( QueryExec qExec = createQueryExec(action, q, dataset); ) {
                 QueryExecResult result = executeQuery(action, qExec, query, queryStringLog);
-                // Deals with exceptions itself.
+                // Deals with response exceptions itself.
                 sendResults(action, result, query.getPrologue());
             }
         }
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/validation/html/IRIValidatorHTML.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/validation/html/IRIValidatorHTML.java
index 003b4aabc3..1e801aeed1 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/validation/html/IRIValidatorHTML.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/validation/html/IRIValidatorHTML.java
@@ -79,10 +79,14 @@ public class IRIValidatorHTML
                         if ( iri.isRelative() )
                             System.out.println("Relative IRI: " + iriStr);
 
-                        iri.handleViolations((error,msg)->{
-                            String str = htmlQuote(msg);
-                            System.out.println(str);
-                        });
+                        if ( iri.hasViolations() ) {
+                            System.out.println();
+                            iri.handleViolations((error,msg)->{
+                                String str = htmlQuote(msg);
+                                String category = (error)?"Error":"Warning";
+                                System.out.printf("  %-7s : %s\n", category, str);
+                            });
+                        }
                     } catch (IRIException ex) {
                         System.out.println(iriStr);
                         System.out.println("Bad IRI: "+ex.getMessage());


Test patch:
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/net/TestHost.java b/jena-base/src/test/java/org/apache/jena/atlas/net/TestHost.java
index 4cd94688d7..95f1714fdb 100644
--- a/jena-base/src/test/java/org/apache/jena/atlas/net/TestHost.java
+++ b/jena-base/src/test/java/org/apache/jena/atlas/net/TestHost.java
@@ -47,7 +47,7 @@ public class TestHost {
         // Assumes the machine has some kind of IP networking.
         try {
             // InetAddress.toString is "host/address"
-            String addr = Host.getHostAddress();
+            String addr = Host.getHostAddressForIRI();
             assertNotNull(addr);
             assertFalse(addr.contains("/"));
         } catch ( RuntimeIOException ex) {
diff --git a/jena-core/src/test/java/org/apache/jena/rdf/model/test/AbstractTestPackage.java b/jena-core/src/test/java/org/apache/jena/rdf/model/test/AbstractTestPackage.java
index eb1ac6cf69..c9de20c268 100644
--- a/jena-core/src/test/java/org/apache/jena/rdf/model/test/AbstractTestPackage.java
+++ b/jena-core/src/test/java/org/apache/jena/rdf/model/test/AbstractTestPackage.java
@@ -109,7 +109,7 @@ public abstract class AbstractTestPackage extends TestSuite
 		addTest(TestRemoveSPO.class, modelFactory);
 		addTest(TestListSubjectsEtc.class, modelFactory);
 		addTest(TestModelRead.class, modelFactory);
-		addTestSuite(TestPropertyImpl.class);
+		addTestSuite(TestProperties.class);
 		addTest(TestContainerConstructors.class, modelFactory);
 		addTest(TestAltMethods.class, modelFactory);
 		addTest(TestBagMethods.class, modelFactory);
diff --git a/jena-core/src/test/java/org/apache/jena/rdf/model/test/TestPropertyImpl.java b/jena-core/src/test/java/org/apache/jena/rdf/model/test/TestProperties.java
similarity index 96%
rename from jena-core/src/test/java/org/apache/jena/rdf/model/test/TestPropertyImpl.java
rename to jena-core/src/test/java/org/apache/jena/rdf/model/test/TestProperties.java
index 0c4429dfa8..cfc109d100 100644
--- a/jena-core/src/test/java/org/apache/jena/rdf/model/test/TestPropertyImpl.java
+++ b/jena-core/src/test/java/org/apache/jena/rdf/model/test/TestProperties.java
@@ -25,9 +25,9 @@ import org.apache.jena.vocabulary.RDFS ;
 import org.junit.Assert;
 import junit.framework.TestCase;
 
-public class TestPropertyImpl extends TestCase
+public class TestProperties extends TestCase
 {
-	public TestPropertyImpl( final String name )
+	public TestProperties( final String name )
 	{
 		super(name);
 	}
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java
index 30b12be6ee..e0bfd9e47a 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java
@@ -51,7 +51,7 @@ import org.apache.jena.sparql.exec.http.GSP;
 import org.apache.jena.sparql.exec.http.QueryExecHTTP;
 
 public class TestModShiro {
-    static final String unlocal = Host.getHostAddress();
+    static final String unlocal = determineUnlocal();
     static final String localRE = Pattern.quote("localhost");
 
     static {
@@ -72,10 +72,16 @@ public class TestModShiro {
         FusekiServerCtl.clearUpSystemState();
     }
 
+    private static String determineUnlocal() {
+        // Get a string for the host in a URL that names this machine but isn't localhost.
+        return Host.getHostAddressForIRI();
+    }
+
     private String unlocalhost(FusekiServer server, String dataset) {
         String local  = server.datasetURL(dataset);
-        if ( unlocal != null )
+        if ( unlocal != null ) {
             local = local.replaceFirst(localRE, unlocal);
+        }
         return local;
     }