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 2022/02/20 14:51:06 UTC

[jena] branch main updated: JENA-2287: Content negotiation for success response page (for SPARQL Update)

This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git


The following commit(s) were added to refs/heads/main by this push:
     new efba196  JENA-2287: Content negotiation for success response page (for SPARQL Update)
     new f852ef1  Merge pull request #1201 from afs/fuseki-update-response
efba196 is described below

commit efba19666c444bbf388e02b855d8047e216dbd60
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sat Feb 19 19:27:48 2022 +0000

    JENA-2287: Content negotiation for success response page (for SPARQL Update)
---
 .../org/apache/jena/atlas/web/ContentType.java     |  69 ++++----
 .../java/org/apache/jena/atlas/web/MediaType.java  | 181 ++++++++++-----------
 .../java/org/apache/jena/query/QueryExecution.java |   1 -
 .../apache/jena/fuseki/servlets/ServletOps.java    |  71 ++++++++
 4 files changed, 196 insertions(+), 126 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/web/ContentType.java b/jena-arq/src/main/java/org/apache/jena/atlas/web/ContentType.java
index 6214055..6c5a320 100644
--- a/jena-arq/src/main/java/org/apache/jena/atlas/web/ContentType.java
+++ b/jena-arq/src/main/java/org/apache/jena/atlas/web/ContentType.java
@@ -18,81 +18,92 @@
 
 package org.apache.jena.atlas.web ;
 
-/** A restricted view of MediaType */
-public class ContentType
-{
-    private MediaType           mediaType ;
-    private static final String charsetParamName = "charset" ;
+import static org.apache.jena.atlas.lib.Lib.equalsIgnoreCase;
+
+import org.apache.jena.riot.web.HttpNames;
+
+/** A restricted view of MediaType: type, subtype and charset. */
+public class ContentType {
+    private MediaType mediaType;
 
     public static ContentType create(String string) {
         if ( string == null )
-            return null ;
-        ContentType ct = new ContentType(MediaType.create(string)) ;
-        return ct ;
+            return null;
+        ContentType ct = new ContentType(MediaType.create(string));
+        return ct;
     }
 
     public static ContentType create(String ctString, String charset) {
-        MediaType.ParsedMediaType x = MediaType.parse(ctString) ;
-        x.params.put(charsetParamName, charset) ;
-        return new ContentType(new MediaType(x)) ;
+        MediaType.ParsedMediaType x = MediaType.parse(ctString);
+        x.params.put(HttpNames.charset, charset); // Ignore params.
+        return new ContentType(new MediaType(x));
     }
 
     private ContentType(MediaType m) {
-        mediaType = m ;
+        mediaType = m;
     }
 
     /**
      * Get the type/subtype as a string.
+     *
      * @see #toHeaderString toHeaderString for use in HTTP headers.
      */
 
     public String getContentTypeStr() {
-        return mediaType.getContentTypeStr() ;
+        return mediaType.getContentTypeStr();
     }
 
     public String getCharset() {
-        return mediaType.getCharset() ;
+        return mediaType.getCharset();
     }
 
     public String getType() {
-        return mediaType.getType() ;
+        return mediaType.getType();
     }
 
     public String getSubType() {
-        return mediaType.getSubType() ;
+        return mediaType.getSubType();
+    }
+
+    /**
+     * Return true if the media type has same type and subtype.
+     */
+    public boolean agreesWith(MediaType mt) {
+        return equalsIgnoreCase(mediaType.getType(), mt.getType()) &&
+               equalsIgnoreCase(mediaType.getSubType(), mt.getSubType());
     }
 
     public String toHeaderString() {
-        return mediaType.toHeaderString() ;
+        return mediaType.toHeaderString();
     }
 
     @Override
     public int hashCode() {
-        final int prime = 31 ;
-        int result = 1 ;
-        result = prime * result + ((mediaType == null) ? 0 : mediaType.hashCode()) ;
-        return result ;
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mediaType == null) ? 0 : mediaType.hashCode());
+        return result;
     }
 
     @Override
     public boolean equals(Object obj) {
         if ( this == obj )
-            return true ;
+            return true;
         if ( obj == null )
-            return false ;
+            return false;
         if ( getClass() != obj.getClass() )
-            return false ;
-        ContentType other = (ContentType)obj ;
+            return false;
+        ContentType other = (ContentType)obj;
         if ( mediaType == null ) {
             if ( other.mediaType != null )
-                return false ;
+                return false;
         } else if ( !mediaType.equals(other.mediaType) )
-            return false ;
-        return true ;
+            return false;
+        return true;
     }
 
     @Override
     public String toString() {
-        return mediaType.toString() ;
+        return mediaType.toString();
     }
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/web/MediaType.java b/jena-arq/src/main/java/org/apache/jena/atlas/web/MediaType.java
index 5230828..de62433 100644
--- a/jena-arq/src/main/java/org/apache/jena/atlas/web/MediaType.java
+++ b/jena-arq/src/main/java/org/apache/jena/atlas/web/MediaType.java
@@ -16,197 +16,186 @@
  * limitations under the License.
  */
 
-package org.apache.jena.atlas.web ;
+package org.apache.jena.atlas.web;
 
-import static org.apache.jena.atlas.lib.Lib.hashCodeObject ;
+import static org.apache.jena.atlas.lib.Lib.hashCodeObject;
 
-import java.util.LinkedHashMap ;
-import java.util.Map ;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Objects;
 
-import org.slf4j.Logger ;
-import org.slf4j.LoggerFactory ;
+import org.apache.jena.riot.web.HttpNames;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
- * A structure to represent a <a
- * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">media
- * type</a>. See also the <a
- * href="http://httpd.apache.org/docs/current/content-negotiation.html">Apache
+ * A structure to represent a
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">media
+ * type</a>. See also the
+ * <a href="http://httpd.apache.org/docs/current/content-negotiation.html">Apache
  * httpd documentation</a>.
+ * <p>
+ * MediaType is the general form, with parameters. ContentType is limited to type,
+ * subtype and charset.
  */
 public class MediaType {
-    private static Logger             log        = LoggerFactory.getLogger(MediaType.class) ;
+    private static Logger log = LoggerFactory.getLogger(MediaType.class);
 
-    private static final String       strCharset = "charset" ;
-
-    private final String              type ;
-    private final String              subType ;
+    private final String type;
+    private final String subType;
     // Keys in insertion order.
-    private final Map<String, String> params ;
+    private final Map<String, String> params;
 
     protected MediaType(ParsedMediaType parser) {
-        this.type = parser.type ;
-        this.subType = parser.subType ;
-        this.params = parser.params ;
+        this.type = parser.type;
+        this.subType = parser.subType;
+        this.params = parser.params;
     }
 
     public MediaType(MediaType other) {
-        this.type = other.type ;
-        this.subType = other.subType ;
+        this.type = other.type;
+        this.subType = other.subType;
         // Order preserving copy.
-        this.params = new LinkedHashMap<>(other.params) ;
+        this.params = new LinkedHashMap<>(other.params);
     }
 
     /** Create a media type from type and subType */
     public MediaType(String type, String subType) {
-        this(type, subType, null) ;
+        this(type, subType, null);
     }
 
     /** Create a media type from type and subType */
     public MediaType(String type, String subType, String charset) {
-        this.type = type ;
-        this.subType = subType ;
-        this.params = new LinkedHashMap<>() ;
+        this.type = type;
+        this.subType = subType;
+        this.params = new LinkedHashMap<>();
         if ( charset != null )
-            setParameter(strCharset, charset) ;
+            setParameter(HttpNames.charset, charset);
     }
 
     public static MediaType create(String contentType, String charset) {
-        ParsedMediaType mediaType = parse(contentType) ;
+        ParsedMediaType mediaType = parse(contentType);
         if ( charset != null )
-            mediaType.params.put(strCharset, charset) ;
-        return new MediaType(mediaType) ;
+            mediaType.params.put(HttpNames.charset, charset);
+        return new MediaType(mediaType);
     }
 
     public static MediaType createFromContentType(String string) {
-        return new MediaType(parse(string)) ;
+        return new MediaType(parse(string));
     }
 
     public static MediaType create(String contentType, String subType, String charset) {
-        return new MediaType(contentType, subType, charset) ;
+        return new MediaType(contentType, subType, charset);
     }
 
     public static MediaType create(String string) {
         if ( string == null )
-            return null ;
-        return new MediaType(parse(string)) ;
+            return null;
+        return new MediaType(parse(string));
     }
 
     public static ParsedMediaType parse(String string) {
-        ParsedMediaType mt = new ParsedMediaType() ;
+        ParsedMediaType mt = new ParsedMediaType();
 
-        String[] x = WebLib.split(string, ";") ;
-        String[] t = WebLib.split(x[0], "/") ;
-        mt.type = t[0] ;
+        String[] x = WebLib.split(string, ";");
+        String[] t = WebLib.split(x[0], "/");
+        mt.type = t[0];
         if ( t.length > 1 )
-            mt.subType = t[1] ;
+            mt.subType = t[1];
 
-        for (int i = 1; i < x.length; i++) {
+        for ( int i = 1 ; i < x.length ; i++ ) {
             // Each a parameter
-            String z[] = WebLib.split(x[i], "=") ;
+            String z[] = WebLib.split(x[i], "=");
             if ( z.length == 2 )
-                mt.params.put(z[0], z[1]) ;
+                mt.params.put(z[0], z[1]);
             else
-                log.warn("Duff parameter: " + x[i] + " in " + string) ;
+                log.warn("Duff parameter: " + x[i] + " in " + string);
         }
-        return mt ;
+        return mt;
     }
 
     /** Format for use in HTTP header */
-
     public String toHeaderString() {
-        StringBuilder b = new StringBuilder() ;
-        b.append(type) ;
+        StringBuilder b = new StringBuilder();
+        b.append(type);
         if ( subType != null )
-            b.append("/").append(subType) ;
+            b.append("/").append(subType);
 
-        for (Map.Entry<String, String> entry : params.entrySet()) {
-            b.append(";") ;
-            b.append(entry.getKey()) ;
-            b.append("=") ;
-            b.append(entry.getValue()) ;
+        for ( Map.Entry<String, String> entry : params.entrySet() ) {
+            b.append(";");
+            b.append(entry.getKey());
+            b.append("=");
+            b.append(entry.getValue());
         }
-        return b.toString() ;
+        return b.toString();
     }
 
     /**
-     * Format to show structure - intentionally different from header form so
-     * you can tell parsing happened correctly
+     * Format to show structure - intentionally different from header form so you can
+     * tell parsing happened correctly
      */
 
     @Override
     public String toString() {
-        StringBuilder b = new StringBuilder() ;
-        b.append("[") ;
-        b.append(type) ;
+        StringBuilder b = new StringBuilder();
+        b.append("[");
+        b.append(type);
         if ( subType != null )
-            b.append("/").append(subType) ;
-        for (String k : params.keySet()) {
+            b.append("/").append(subType);
+        for ( String k : params.keySet() ) {
             if ( k.equals("boundary") )
-                continue ;
-            String v = params.get(k) ;
-            b.append(" ") ;
-            b.append(k) ;
-            b.append("=") ;
-            b.append(v) ;
+                continue;
+            String v = params.get(k);
+            b.append(" ");
+            b.append(k);
+            b.append("=");
+            b.append(v);
         }
-        b.append("]") ;
-        return b.toString() ;
+        b.append("]");
+        return b.toString();
     }
 
-    // private String type = null ;
-    // private String subType = null ;
-    // // Keys in insertion order.
-    // private Map<String, String> params = new LinkedHashMap<String, String>()
-    // ;
-
     @Override
     public int hashCode() {
-        return hashCodeObject(type, 1) ^ hashCodeObject(subType, 2) ^ hashCodeObject(params, 3) ;
+        return hashCodeObject(type, 1) ^ hashCodeObject(subType, 2) ^ hashCodeObject(params, 3);
     }
 
     @Override
     public boolean equals(Object object) {
         if ( this == object )
-            return true ;
+            return true;
         if ( !(object instanceof MediaType) )
-            return false ;
-        MediaType mt = (MediaType)object ;
-        return Objects.equals(type, mt.type) && Objects.equals(subType, mt.subType) && Objects.equals(params, mt.params) ;
+            return false;
+        MediaType mt = (MediaType)object;
+        return Objects.equals(type, mt.type) && Objects.equals(subType, mt.subType) && Objects.equals(params, mt.params);
     }
 
     public String getParameter(String name) {
-        return params.get(name) ;
+        return params.get(name);
     }
 
     private void setParameter(String name, String value) {
-        params.put(name, value) ;
-    }
-
-    /** @deprecated Use {@link #getContentTypeStr} */
-    @Deprecated
-    public String getContentType() {
-        return getContentTypeStr();
+        params.put(name, value);
     }
 
     public String getContentTypeStr() {
         if ( subType == null )
-            return type ;
-        return type + "/" + subType ;
+            return type;
+        return type + "/" + subType;
     }
 
     public String getCharset() {
-        return getParameter(strCharset) ;
+        return getParameter(HttpNames.charset);
     }
 
     public String getSubType() {
-        return subType ;
+        return subType;
     }
 
     // public void setSubType(String subType) { this.subType = subType ;
     // strContentType = null ; }
     public String getType() {
-        return type ;
+        return type;
     }
     // public void setType(String type) { this.type = type ; strContentType =
     // null ; }
@@ -217,8 +206,8 @@ public class MediaType {
      * @see MediaType#parse
      */
     /* package */static class ParsedMediaType {
-        public String              type ;
-        public String              subType ;
-        public Map<String, String> params = new LinkedHashMap<>() ;
+        public String type;
+        public String subType;
+        public Map<String, String> params = new LinkedHashMap<>();
     }
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/query/QueryExecution.java b/jena-arq/src/main/java/org/apache/jena/query/QueryExecution.java
index 5a99d90..5afd2c8 100644
--- a/jena-arq/src/main/java/org/apache/jena/query/QueryExecution.java
+++ b/jena-arq/src/main/java/org/apache/jena/query/QueryExecution.java
@@ -66,7 +66,6 @@ public interface QueryExecution extends AutoCloseable
         return QueryExecution.service(endpointURL).query(queryString).build();
     }
 
-
     // Short cuts to QueryExecution builders.
 
     /** Create a local execution builder on a dataset */
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 a111dfd..2bab790 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
@@ -20,6 +20,7 @@ package org.apache.jena.fuseki.servlets;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 
 import javax.servlet.ServletOutputStream;
@@ -28,7 +29,12 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.jena.atlas.RuntimeIOException;
 import org.apache.jena.atlas.io.IO;
 import org.apache.jena.atlas.json.JSON;
+import org.apache.jena.atlas.json.JsonBuilder;
+import org.apache.jena.atlas.json.JsonObject;
 import org.apache.jena.atlas.json.JsonValue;
+import org.apache.jena.atlas.web.AcceptList;
+import org.apache.jena.atlas.web.MediaType;
+import org.apache.jena.fuseki.system.ConNeg;
 import org.apache.jena.fuseki.system.UploadDetails;
 import org.apache.jena.fuseki.system.UploadDetails.PreState;
 import org.apache.jena.riot.RiotParseException;
@@ -109,7 +115,72 @@ public class ServletOps {
         action.setResponseStatus(httpStatusCode);
     }
 
+    private static MediaType mtTextHTML = MediaType.create("text/html");
+    private static AcceptList successReponseAccept = AcceptList.create("text/html", WebContent.contentTypeTextPlain, WebContent.contentTypeJSON);
+
     public static void successPage(HttpAction action, String message) {
+        String x = action.getRequestHeader(HttpNames.hAccept);
+        MediaType mt = null ;
+        if ( x != null && x.equals("*/*") ) {
+            if ( action.getRequestContentType().equals(WebContent.contentTypeHTMLForm))
+                mt = mtTextHTML;
+        }
+
+        if ( mt == null && x != null )
+            mt = ConNeg.chooseContentType(action.getRequest(), successReponseAccept, MediaType.create("text/plain"));
+
+        if ( mt == null )
+            mt = mtTextHTML;
+
+        if ( WebContent.ctTextPlain.agreesWith(mt) ) {
+            successPageText(action, message);
+            return ;
+        }
+        if ( WebContent.ctJSON.agreesWith(mt) ) {
+            successPageJson(action, message);
+            return ;
+        }
+        // Default.
+        successPageHtml(action, message);
+    }
+
+    private static void successPageJson(HttpAction action, String message) {
+        try {
+            action.setResponseContentType(WebContent.contentTypeJSON);
+            action.setResponseCharacterEncoding(WebContent.charsetUTF8);
+            action.setResponseStatus(HttpSC.OK_200);
+            OutputStream out = action.getResponseOutputStream();
+            JsonObject obj = JsonBuilder.buildObject(builder->{
+                builder.pair("statusCode", 200);
+                if ( message != null )
+                    builder.pair("message", message);
+            });
+            JSON.write(out, obj);
+            return;
+        } catch (IOException ex) {
+            errorOccurred(ex);
+            return;
+        }
+    }
+
+    private static void successPageText(HttpAction action, String message) {
+        if ( message == null )
+            message = HttpSC.getMessage(HttpSC.OK_200);
+        try {
+            action.setResponseContentType("text/plain");
+            action.setResponseCharacterEncoding(WebContent.charsetUTF8);
+            action.setResponseStatus(HttpSC.OK_200);
+            PrintWriter out = action.getResponseWriter();
+            if ( message != null )
+                out.println(message);
+            return;
+        } catch (IOException ex) {
+            errorOccurred(ex);
+            return;
+        }
+    }
+
+    private static void successPageHtml(HttpAction action, String message) {
         try {
             action.setResponseContentType("text/html");
             action.setResponseCharacterEncoding(WebContent.charsetUTF8);