You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2020/10/16 14:24:21 UTC

[isis] branch master updated: ISIS-2449: simplified representation reworked for v2

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

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new 0e19e91  ISIS-2449: simplified representation reworked for v2
0e19e91 is described below

commit 0e19e91b5ac504c00ce9551b66cf88542a4b8d5c
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Oct 16 16:24:00 2020 +0200

    ISIS-2449: simplified representation reworked for v2
    
    "urn:org.apache.isis/v2" now is fully responsible for all request types
    and no longer falls through to non-simplified representation
    
    that means, it now renders
    - list of values
    - scalar value
    - void
---
 .../isis/applib/util/schema/ScalarValueDtoV2.java  |  47 +++++----
 .../isis/extensions/restclient/ResponseDigest.java |  31 +++---
 .../{ScalarValueDto.java => ScalarValueDtoV1.java} |   6 +-
 ...=> RestServiceSimpifiedRepresentationTest.java} |  12 ++-
 .../isis/testdomain/rospec/RoSpecSampler.java      |   7 ++
 .../ContentNegotiationServiceOrgApacheIsisV2.java  | 106 ++++++++++++++++-----
 6 files changed, 156 insertions(+), 53 deletions(-)

diff --git a/mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ScalarValueDto.java b/api/applib/src/main/java/org/apache/isis/applib/util/schema/ScalarValueDtoV2.java
similarity index 50%
copy from mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ScalarValueDto.java
copy to api/applib/src/main/java/org/apache/isis/applib/util/schema/ScalarValueDtoV2.java
index 08589f1..54b6b54 100644
--- a/mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ScalarValueDto.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/util/schema/ScalarValueDtoV2.java
@@ -16,27 +16,40 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.extensions.restclient;
+package org.apache.isis.applib.util.schema;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 
-import lombok.Getter;
-import lombok.Setter;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
 
-@JsonIgnoreProperties({"links", "resulttype"})
-class ScalarValueDto {
-    
-    @JsonIgnoreProperties({"links", "extensions"})
-    public static class ScalarValue {
-        @Getter @Setter private Object value;
-    }
-
-    @Getter @Setter private ScalarValue result;
-    
-    @JsonIgnore
-    public Object getValue() {
-        return result!=null ? result.getValue() : null;
-    }
+/**
+ * Represents a nullable scalar value,
+ * as used by ContentNegotiationServiceOrgApacheIsisV2 and its clients.
+ * @since Oct 16, 2020
+ */
+@JsonIgnoreProperties({"links", "extensions"})
+@Data @NoArgsConstructor @AllArgsConstructor(access = AccessLevel.PRIVATE)
+public final class ScalarValueDtoV2 {
+   
+   public static ScalarValueDtoV2 forNull(@NonNull Class<?> type) {
+       return new ScalarValueDtoV2(type.getSimpleName(), null);
+   }
     
+   public static ScalarValueDtoV2 forValue(@NonNull Object value) {
+       return new ScalarValueDtoV2(value.getClass().getSimpleName(), value);
+   }
+   
+   private String type;
+   private Object value;
+   
+   @JsonIgnore
+   public boolean isNull() {
+       return value == null;
+   }
+   
 }
\ No newline at end of file
diff --git a/mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ResponseDigest.java b/mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ResponseDigest.java
index 2e88c22..6683bd6 100644
--- a/mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ResponseDigest.java
+++ b/mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ResponseDigest.java
@@ -35,12 +35,13 @@ import javax.ws.rs.core.Response.Status.Family;
 
 import com.fasterxml.jackson.core.JsonParseException;
 import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 
 import org.apache.isis.applib.util.schema.CommonDtoUtils;
+import org.apache.isis.applib.util.schema.ScalarValueDtoV2;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.commons.internal.base._Strings;
-import org.apache.isis.commons.internal.resources._Json;
 
 import lombok.NonNull;
 import lombok.val;
@@ -218,21 +219,30 @@ public class ResponseDigest<T> {
 
     private T readSingle() throws JsonParseException, JsonMappingException, IOException {
         if(isValueType(entityType)) {
-            val responseBody = response.readEntity(String.class);
-            return parseValueTypeBody(responseBody);
+            val mapper = new ObjectMapper();
+            val jsonInput = response.readEntity(String.class);
+            val scalarValueDto = mapper.readValue(jsonInput, ScalarValueDtoV2.class);
+            return extractValue(scalarValueDto);
         }
         return response.<T>readEntity(entityType);
     }
     
     private List<T> readList() throws JsonParseException, JsonMappingException, IOException {
         if(isValueType(entityType)) {
-            final List<String> valueBodies = response.readEntity(new GenericType<List<String>>() {});
-            final List<T> resultList = new ArrayList<>(valueBodies.size());
-            for(val valueBody : valueBodies) {
+            val mapper = new ObjectMapper();
+            val jsonInput = response.readEntity(String.class);
+            final List<ScalarValueDtoV2> scalarValueDtoList = 
+                    mapper.readValue(
+                            jsonInput, 
+                            mapper.getTypeFactory().constructCollectionType(List.class, ScalarValueDtoV2.class));
+            
+            final List<T> resultList = new ArrayList<>(scalarValueDtoList.size());
+            for(val valueBody : scalarValueDtoList) {
                 // explicit loop, for simpler exception propagation
-                resultList.add(parseValueTypeBody(valueBody)); 
+                resultList.add(extractValue(valueBody)); 
             }
             return resultList;
+
         }
         return response.readEntity(genericType);
     }
@@ -280,12 +290,11 @@ public class ResponseDigest<T> {
     private boolean isValueType(Class<T> entityType) {
         return CommonDtoUtils.isValueType(entityType);
     }
-
-    private T parseValueTypeBody(String body) 
+    
+    private T extractValue(ScalarValueDtoV2 scalarValueDto) 
             throws JsonParseException, JsonMappingException, IOException {
-
-        val scalarValueDto = _Json.readJson(ScalarValueDto.class, body);
         return _Casts.uncheckedCast(scalarValueDto.getValue());
     }
 
+
 }
diff --git a/mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ScalarValueDto.java b/mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ScalarValueDtoV1.java
similarity index 93%
rename from mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ScalarValueDto.java
rename to mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ScalarValueDtoV1.java
index 08589f1..cf7e4e6 100644
--- a/mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ScalarValueDto.java
+++ b/mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient/ScalarValueDtoV1.java
@@ -24,8 +24,12 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import lombok.Getter;
 import lombok.Setter;
 
+/**
+ * @deprecated remove with 2.0.0 if no longer needed
+ */
+@Deprecated
 @JsonIgnoreProperties({"links", "resulttype"})
-class ScalarValueDto {
+class ScalarValueDtoV1 {
     
     @JsonIgnoreProperties({"links", "extensions"})
     public static class ScalarValue {
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/rest/RestServiceRoSpecTest.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/rest/RestServiceSimpifiedRepresentationTest.java
similarity index 96%
rename from regressiontests/incubating/src/test/java/org/apache/isis/testdomain/rest/RestServiceRoSpecTest.java
rename to regressiontests/incubating/src/test/java/org/apache/isis/testdomain/rest/RestServiceSimpifiedRepresentationTest.java
index 49a1a9f..fed8154 100644
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/rest/RestServiceRoSpecTest.java
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/rest/RestServiceSimpifiedRepresentationTest.java
@@ -61,7 +61,7 @@ import lombok.val;
     Configuration_usingRoSpec.class,
     IsisModuleViewerRestfulObjectsJaxrsResteasy4.class,
 })
-class RestServiceRoSpecTest {
+class RestServiceSimpifiedRepresentationTest {
 
     @LocalServerPort int port; // just for reference (not used)
     @Inject RestEndpointService restService;
@@ -71,6 +71,14 @@ class RestServiceRoSpecTest {
     // -- STRING
     
     @Test
+    void voidResult() {
+        val digest = digest("voidResult", void.class);
+        assertTrue(digest.getEntities().isEmpty());
+    }
+    
+    // -- STRING
+    
+    @Test
     void string() {
         val digest = digest("string", String.class);
         val returnValue = digest.getEntities().getSingletonOrFail();
@@ -88,7 +96,7 @@ class RestServiceRoSpecTest {
     @Test 
     void stringArray() {
         val digest = digestList("stringArray", String.class, new GenericType<List<String>>(){});
-        val returnValue = digest.getEntities().getSingletonOrFail();
+        val returnValue = digest.getEntities();
         assertComponentWiseEquals(refSampler.stringArray(), returnValue);
     }
     
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/rospec/RoSpecSampler.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/rospec/RoSpecSampler.java
index 7c138d9..48f2322 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/rospec/RoSpecSampler.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/rospec/RoSpecSampler.java
@@ -30,6 +30,13 @@ import org.apache.isis.commons.internal.collections._Lists;
         objectType = "testdomain.RoSpecSampler")
 public class RoSpecSampler {
 
+    // -- VOID
+    
+    @Action 
+    public void voidResult() {
+        
+    }
+    
     // -- STRING
     
     @Action 
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV2.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV2.java
index fcae6e8..4c97542 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV2.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV2.java
@@ -26,12 +26,17 @@ import javax.inject.Named;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
+import com.fasterxml.jackson.databind.node.POJONode;
+
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.OrderPrecedence;
 import org.apache.isis.applib.client.SuppressionType;
+import org.apache.isis.applib.util.schema.ScalarValueDtoV2;
+import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.metamodel.consent.Consent;
 import org.apache.isis.core.metamodel.facets.collections.CollectionFacet;
 import org.apache.isis.core.metamodel.interactions.managed.ManagedAction;
@@ -57,7 +62,6 @@ import lombok.val;
 @Qualifier("OrgApacheIsisV2")
 public class ContentNegotiationServiceOrgApacheIsisV2 extends ContentNegotiationServiceAbstract {
 
-
     /**
      * Unlike RO v1.0, use a single content-type of <code>application/json;profile="urn:org.apache.isis/v2"</code>.
      *
@@ -95,6 +99,29 @@ public class ContentNegotiationServiceOrgApacheIsisV2 extends ContentNegotiation
             + "profile=\"" + ACCEPT_PROFILE + "\""
             + ";repr-type=\"list\""
             ;
+    
+    /**
+     * The media type (as a string) used as the content-Type header when a single (nullable) value is rendered.
+     *
+     * @see #ACCEPT_PROFILE for discussion.
+     * @since 2.0
+     */
+    public static final String CONTENT_TYPE_OAI_V2_VALUE = "application/json;"
+            + "profile=\"" + ACCEPT_PROFILE + "\""
+            + ";repr-type=\"value\""
+            ;
+    
+    /**
+     * The media type (as a string) used as the content-Type header when a list of values is rendered. 
+     * Also used for action return type void, which is represented by an empty list.
+     *
+     * @see #ACCEPT_PROFILE for discussion.
+     * @since 2.0
+     */
+    public static final String CONTENT_TYPE_OAI_V2_VALUES = "application/json;"
+            + "profile=\"" + ACCEPT_PROFILE + "\""
+            + ";repr-type=\"values\""
+            ;
 
     private final ContentNegotiationServiceForRestfulObjectsV1_0 restfulObjectsV1_0;
 
@@ -232,6 +259,8 @@ public class ContentNegotiationServiceOrgApacheIsisV2 extends ContentNegotiation
         final ManagedObject returnedAdapter = objectAndActionInvocation.getReturnedAdapter();
 
         final ActionResultRepresentation.ResultType resultType = objectAndActionInvocation.determineResultType();
+        final String resultTypeLiteral;
+        
         switch (resultType) {
         case DOMAIN_OBJECT:
 
@@ -244,43 +273,76 @@ public class ContentNegotiationServiceOrgApacheIsisV2 extends ContentNegotiation
                 appendObjectTo(resourceContext, returnedAdapter, rootRepresentation, suppression);
             }
 
+            resultTypeLiteral = CONTENT_TYPE_OAI_V2_OBJECT;
+            
             break;
 
         case LIST:
 
             rootRepresentation = JsonRepresentation.newArray();
             
-            CollectionFacet.streamAdapters(returnedAdapter)
-            .forEach(element->
-                appendElementTo(resourceContext, element, rootRepresentation, suppression));
+            val elementAdapters = CollectionFacet.streamAdapters(returnedAdapter)
+            .collect(_Lists.toUnmodifiable());
+            
+            val isListOfIndentifiable = elementAdapters.stream()
+            .allMatch(elementAdapter->elementAdapter.getSpecification().isIdentifiable());
+            
+            if(isListOfIndentifiable) {
+                
+                elementAdapters
+                .forEach(elementAdapter->
+                    appendElementTo(resourceContext, elementAdapter, rootRepresentation, suppression));
+                
+                resultTypeLiteral = CONTENT_TYPE_OAI_V2_LIST;
 
-            // $$ro representation will be an object in the list with a single property named "$$ro"
-            if(!suppressRO) {
-                JsonRepresentation $$roContainerRepresentation = JsonRepresentation.newMap();
-                rootRepresentation.arrayAdd($$roContainerRepresentation);
-                $$roContainerRepresentation.mapPut("$$ro", $$roRepresentation);
+                // $$ro representation will be an object in the list with a single property named "$$ro"
+                if(!suppressRO) {
+                    JsonRepresentation $$roContainerRepresentation = JsonRepresentation.newMap();
+                    rootRepresentation.arrayAdd($$roContainerRepresentation);
+                    $$roContainerRepresentation.mapPut("$$ro", $$roRepresentation);
+                }
+                
+            } else {
+                
+                elementAdapters.stream()
+                .map(elementAdapter->{
+                    val pojo = elementAdapter.getPojo();
+                    return pojo==null
+                        ? ScalarValueDtoV2.forNull(elementAdapter.getSpecification().getCorrespondingClass())
+                        : ScalarValueDtoV2.forValue(pojo);
+                })
+                .forEach(rootRepresentation::arrayAdd);
+                
+                resultTypeLiteral = CONTENT_TYPE_OAI_V2_VALUES;
+                
             }
 
             break;
 
         case SCALAR_VALUE:
+            
+            val pojo = returnedAdapter.getPojo();
+            val dto =  pojo==null
+                ? ScalarValueDtoV2.forNull(returnedAdapter.getSpecification().getCorrespondingClass())
+                : ScalarValueDtoV2.forValue(pojo);
+                
+            rootRepresentation = new JsonRepresentation(new POJONode(dto));
+            resultTypeLiteral = CONTENT_TYPE_OAI_V2_VALUE;
+            
+            break;
+            
         case VOID:
-
-            // not supported
-            return null;
+            // represented as empty array
+            rootRepresentation = JsonRepresentation.newArray();
+            resultTypeLiteral = CONTENT_TYPE_OAI_V2_VALUES;
+            break;
         default:
-            rootRepresentation = null; // unexpected code reach
+            throw _Exceptions.unmatchedCase(resultType);
         }
 
-        final Response.ResponseBuilder responseBuilder =
-                restfulObjectsV1_0.buildResponseTo(
-                        resourceContext, objectAndActionInvocation, $$roRepresentation, rootRepresentation);
-
-        // set appropriate Content-Type
-        responseBuilder.type(
-                resultType == ActionResultRepresentation.ResultType.DOMAIN_OBJECT
-                        ? CONTENT_TYPE_OAI_V2_OBJECT
-                        : CONTENT_TYPE_OAI_V2_LIST);
+        val responseBuilder = restfulObjectsV1_0
+                .buildResponseTo(resourceContext, objectAndActionInvocation, $$roRepresentation, rootRepresentation)
+                .type(resultTypeLiteral);  // set appropriate Content-Type
 
         return responseBuilder(responseBuilder);
     }