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);
}