You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2023/02/24 11:01:07 UTC
[causeway] 01/01: CAUSEWAY-3330: adds composite value RO test case
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch 3330-RO.composite.values
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 482e51140d2b572e8808ff9288953dc837b00648
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Feb 24 12:01:00 2023 +0100
CAUSEWAY-3330: adds composite value RO test case
- yet the REST transport is done using a base64 encoded string (in both
directions)
---
.../applib/value/semantics/ValueDecomposition.java | 12 ++-
.../applib/value/CalendarEventSemantics.java | 2 +-
.../causeway/testdomain/rest/RestServiceTest.java | 92 +++++++++-------------
.../testdomain/jdo/JdoInventoryResource.java | 8 +-
.../testdomain/util/rest/RestEndpointService.java | 24 ++++++
.../applib/dtos/ScalarValueDtoV2.java | 15 +++-
.../client/ActionParameterListBuilder.java | 6 ++
.../restfulobjects/client/ResponseDigest.java | 9 +++
.../restfulobjects/client/RestfulClient.java | 2 +-
...ntentNegotiationServiceOrgApacheCausewayV2.java | 47 +++++++----
.../JsonValueEncoderServiceDefault.java | 12 +++
.../viewer/resources/JsonParserHelper.java | 4 +-
12 files changed, 153 insertions(+), 80 deletions(-)
diff --git a/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueDecomposition.java b/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueDecomposition.java
index 7f314ff46c..ec957cc094 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueDecomposition.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/value/semantics/ValueDecomposition.java
@@ -23,6 +23,7 @@ import java.io.Serializable;
import org.apache.causeway.applib.util.schema.CommonDtoUtils;
import org.apache.causeway.commons.functional.Either;
import org.apache.causeway.commons.functional.Either.HasEither;
+import org.apache.causeway.commons.internal.base._Strings;
import org.apache.causeway.schema.common.v2.TypedTupleDto;
import org.apache.causeway.schema.common.v2.ValueType;
import org.apache.causeway.schema.common.v2.ValueWithTypeDto;
@@ -50,7 +51,7 @@ implements
}
/**
- * In support of JAXB de-serialization,
+ * In support of JAXB de-serialization,
* returns an unspecified type.
* (Introduced for the CalendarEvent demo to work.)
* @deprecated not sure why we are hitting this; remove eventually
@@ -76,4 +77,13 @@ implements
: ofFundamental(CommonDtoUtils.getFundamentalValueFromJson(vType, json));
}
+ // for transport over REST
+ public String stringify() {
+ return _Strings.base64UrlEncodeZlibCompressed(toJson());
+ }
+ // for transport over REST
+ public static ValueDecomposition destringify(final ValueType vType, final String string) {
+ return fromJson(vType, _Strings.base64UrlDecodeZlibCompressed(string));
+ }
+
}
\ No newline at end of file
diff --git a/extensions/vw/fullcalendar/applib/src/main/java/org/apache/causeway/extensions/fullcalendar/applib/value/CalendarEventSemantics.java b/extensions/vw/fullcalendar/applib/src/main/java/org/apache/causeway/extensions/fullcalendar/applib/value/CalendarEventSemantics.java
index 749e11d392..b6d63fff28 100644
--- a/extensions/vw/fullcalendar/applib/src/main/java/org/apache/causeway/extensions/fullcalendar/applib/value/CalendarEventSemantics.java
+++ b/extensions/vw/fullcalendar/applib/src/main/java/org/apache/causeway/extensions/fullcalendar/applib/value/CalendarEventSemantics.java
@@ -183,7 +183,7 @@ implements
ZonedDateTime.of(2022, 05, 13, 17, 30, 15, 0, ZoneOffset.ofHours(3)),
"Business",
"Weekly Meetup",
- "Calendar Notes");
+ "Calendar Notes: <a href=\"https://apache.org\">apache.org</a>"); // should be properly serialized to JSON
val b = CalendarEvent.of(
ZonedDateTime.of(2022, 06, 14, 18, 31, 16, 0, ZoneOffset.ofHours(4)),
diff --git a/regressiontests/stable-rest/src/test/java/org/apache/causeway/testdomain/rest/RestServiceTest.java b/regressiontests/stable-rest/src/test/java/org/apache/causeway/testdomain/rest/RestServiceTest.java
index 6687032edd..190d456ebc 100644
--- a/regressiontests/stable-rest/src/test/java/org/apache/causeway/testdomain/rest/RestServiceTest.java
+++ b/regressiontests/stable-rest/src/test/java/org/apache/causeway/testdomain/rest/RestServiceTest.java
@@ -22,6 +22,7 @@ import javax.inject.Inject;
import javax.xml.bind.JAXBException;
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
@@ -33,17 +34,22 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.apache.causeway.core.config.presets.CausewayPresets;
+import org.apache.causeway.extensions.fullcalendar.applib.value.CalendarEventSemantics;
import org.apache.causeway.testdomain.conf.Configuration_usingJdo;
import org.apache.causeway.testdomain.jdo.JdoInventoryJaxbVm;
import org.apache.causeway.testdomain.jdo.JdoTestFixtures;
import org.apache.causeway.testdomain.jdo.entities.JdoBook;
import org.apache.causeway.testdomain.util.rest.RestEndpointService;
+import org.apache.causeway.viewer.restfulobjects.client.RestfulClient;
import org.apache.causeway.viewer.restfulobjects.jaxrsresteasy.CausewayModuleViewerRestfulObjectsJaxrsResteasy;
import lombok.val;
@SpringBootTest(
- classes = {RestEndpointService.class},
+ classes = {
+ RestEndpointService.class,
+ CalendarEventSemantics.class // register semantics for testing
+ },
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(CausewayPresets.UseLog4j2Test)
@Import({
@@ -55,12 +61,17 @@ class RestServiceTest {
@LocalServerPort int port; // just for reference (not used)
@Inject RestEndpointService restService;
- @Test
- void httpSessionInfo() {
+ private RestfulClient restfulClient;
- val useRequestDebugLogging = false;
- val restfulClient = restService.newClient(useRequestDebugLogging);
+ @BeforeEach
+ void checkPrereq() {
+ assertTrue(restService.getPort()>0);
+ val useRequestDebugLogging = true;
+ this.restfulClient = restService.newClient(useRequestDebugLogging);
+ }
+ @Test
+ void httpSessionInfo() {
val digest = restService.getHttpSessionInfo(restfulClient)
.ifFailure(Assertions::fail);
@@ -70,17 +81,10 @@ class RestServiceTest {
// NB: this works only because we excluded wicket viewer from the app.
assertEquals("no http-session", httpSessionInfo);
-
}
@Test
void bookOfTheWeek_viaRestEndpoint() {
-
- assertTrue(restService.getPort()>0);
-
- val useRequestDebugLogging = false;
- val restfulClient = restService.newClient(useRequestDebugLogging);
-
val digest = restService.getRecommendedBookOfTheWeek(restfulClient)
.ifFailure(Assertions::fail);
@@ -88,17 +92,10 @@ class RestServiceTest {
assertNotNull(bookOfTheWeek);
assertEquals("Book of the week", bookOfTheWeek.getName());
-
}
@Test
void addNewBook_viaRestEndpoint() throws JAXBException {
-
- assertTrue(restService.getPort()>0);
-
- val useRequestDebugLogging = false;
- val restfulClient = restService.newClient(useRequestDebugLogging);
-
val newBook = JdoBook.of("REST Book", "A sample REST book for testing.", 77.,
"REST Author", "REST ISBN", "REST Publisher");
@@ -109,17 +106,10 @@ class RestServiceTest {
assertNotNull(storedBook);
assertEquals("REST Book", storedBook.getName());
-
}
@Test
void multipleBooks_viaRestEndpoint() throws JAXBException {
-
- assertTrue(restService.getPort()>0);
-
- val useRequestDebugLogging = false;
- val restfulClient = restService.newClient(useRequestDebugLogging);
-
val digest = restService.getMultipleBooks(restfulClient)
.ifFailure(Assertions::fail);
@@ -133,12 +123,6 @@ class RestServiceTest {
@Test
void bookOfTheWeek_asDto_viaRestEndpoint() {
-
- assertTrue(restService.getPort()>0);
-
- val useRequestDebugLogging = false;
- val restfulClient = restService.newClient(useRequestDebugLogging);
-
val digest = restService.getRecommendedBookOfTheWeekAsDto(restfulClient)
.ifFailure(Assertions::fail);
@@ -146,17 +130,10 @@ class RestServiceTest {
assertNotNull(bookOfTheWeek);
assertEquals("Book of the week", bookOfTheWeek.getName());
-
}
@Test
void multipleBooks_asDto_viaRestEndpoint() throws JAXBException {
-
- assertTrue(restService.getPort()>0);
-
- val useRequestDebugLogging = false;
- val restfulClient = restService.newClient(useRequestDebugLogging);
-
val digest = restService.getMultipleBooksAsDto(restfulClient)
.ifFailure(Assertions::fail);
@@ -167,17 +144,10 @@ class RestServiceTest {
for(val book : multipleBooks) {
assertEquals("MultipleBooksAsDtoTest", book.getName());
}
-
}
@Test
void inventoryAsJaxbVm_viaRestEndpoint() {
-
- assertTrue(restService.getPort()>0);
-
- val useRequestDebugLogging = false;
- val restfulClient = restService.newClient(useRequestDebugLogging);
-
val digest = restService.getInventoryAsJaxbVm(restfulClient)
.ifFailure(Assertions::fail);
@@ -185,17 +155,10 @@ class RestServiceTest {
assertNotNull(inventoryAsJaxbVm);
assertEquals("Bookstore", inventoryAsJaxbVm.getName());
-
}
@Test
void listBooks_fromInventoryAsJaxbVm_viaRestEndpoint() {
-
- assertTrue(restService.getPort()>0);
-
- val useRequestDebugLogging = false;
- val restfulClient = restService.newClient(useRequestDebugLogging);
-
val digest = restService.getBooksFromInventoryAsJaxbVm(restfulClient)
.ifFailure(Assertions::fail);
@@ -207,8 +170,29 @@ class RestServiceTest {
.filter(book->expectedBookTitles.contains(book.getName()));
assertEquals(3, multipleBooks.size());
-
}
+ @Test
+ void calendarEvent_echo_viaRestEndpoint() {
+ val calSemantics = new CalendarEventSemantics();
+ val calSample = calSemantics.getExamples().getElseFail(0);
+ /* calSemantics.decompose(calSample).toJson() ...
+ * {
+ * "elements":[
+ * {"long":1652452215000,"type":"long","name":"epochMillis"},
+ * {"string":"Business","type":"string","name":"calendarName"},
+ * {"string":"Weekly Meetup","type":"string","name":"title"},
+ * {"string":"Calendar Notes","type":"string","name":"notes"}
+ * ],
+ * "type":"org.apache.causeway.extensions.fullcalendar.applib.value.CalendarEvent",
+ * "cardinality":4
+ * }
+ */
+ val digest = restService.echoCalendarEvent(restfulClient, calSample)
+ .ifFailure(Assertions::fail);
+
+ val calSampleEchoed = digest.getValue().orElseThrow();
+ assertEquals(calSample, calSampleEchoed);
+ }
}
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/jdo/JdoInventoryResource.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/jdo/JdoInventoryResource.java
index 157325859d..6dcc50f301 100644
--- a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/jdo/JdoInventoryResource.java
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/jdo/JdoInventoryResource.java
@@ -38,6 +38,7 @@ import org.apache.causeway.applib.services.factory.FactoryService;
import org.apache.causeway.applib.services.repository.RepositoryService;
import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.commons.internal.collections._Lists;
+import org.apache.causeway.extensions.fullcalendar.applib.value.CalendarEvent;
import org.apache.causeway.testdomain.jdo.entities.JdoBook;
import org.apache.causeway.testdomain.jdo.entities.JdoProduct;
import org.apache.causeway.testdomain.util.dto.BookDto;
@@ -93,12 +94,17 @@ public class JdoInventoryResource {
return listBooks();
}
- @Action //TODO improve the REST client such that the param can be of type Book
+ @Action //XXX improve the REST client such that the param can be of type JdoBook?
public JdoBook storeBook(final String newBook) throws JAXBException {
val book = JdoBook.fromDto(BookDto.decode(newBook));
return repository.persist(book);
}
+ @Action // echos given CalendarEvent (composite value type test)
+ public CalendarEvent echoCalendarEvent(final CalendarEvent calendarEvent) throws JAXBException {
+ return calendarEvent;
+ }
+
// -- NON - ENTITIES
@Action
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/rest/RestEndpointService.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/rest/RestEndpointService.java
index f8d2ca8ae0..9dd3853dea 100644
--- a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/rest/RestEndpointService.java
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/rest/RestEndpointService.java
@@ -30,10 +30,14 @@ import org.springframework.stereotype.Service;
import org.apache.causeway.applib.client.SuppressionType;
import org.apache.causeway.applib.services.iactnlayer.InteractionService;
+import org.apache.causeway.applib.value.semantics.ValueDecomposition;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.functional.Try;
import org.apache.causeway.core.config.RestEasyConfiguration;
import org.apache.causeway.core.config.viewer.web.WebAppContextPath;
+import org.apache.causeway.extensions.fullcalendar.applib.value.CalendarEvent;
+import org.apache.causeway.extensions.fullcalendar.applib.value.CalendarEventSemantics;
+import org.apache.causeway.schema.common.v2.ValueType;
import org.apache.causeway.testdomain.jdo.JdoInventoryJaxbVm;
import org.apache.causeway.testdomain.jdo.JdoTestFixtures;
import org.apache.causeway.testdomain.jdo.entities.JdoBook;
@@ -227,6 +231,26 @@ public class RestEndpointService {
return digest;
}
+ public Try<CalendarEvent> echoCalendarEvent(
+ final RestfulClient client, final CalendarEvent calendarEvent) {
+
+ val calSemantics = new CalendarEventSemantics();
+
+ val request = newInvocationBuilder(client,
+ INVENTORY_RESOURCE + "/actions/echoCalendarEvent/invoke");
+ val args = client.arguments()
+ .addActionParameter("calendarEvent", calSemantics.decompose(calendarEvent))
+ .build();
+
+ val response = request.post(args);
+
+ val digest = client.digest(response, String.class)
+ .mapSuccess(stringifiedVal -> ValueDecomposition.destringify(ValueType.COMPOSITE, stringifiedVal))
+ .mapSuccess(valDecomposition->calSemantics.compose(valDecomposition));
+
+ return digest;
+ }
+
public Try<String> getHttpSessionInfo(final RestfulClient client) {
val request = newInvocationBuilder(client,
diff --git a/viewers/restfulobjects/applib/src/main/java/org/apache/causeway/viewer/restfulobjects/applib/dtos/ScalarValueDtoV2.java b/viewers/restfulobjects/applib/src/main/java/org/apache/causeway/viewer/restfulobjects/applib/dtos/ScalarValueDtoV2.java
index 5d77a54983..62aef8dccd 100644
--- a/viewers/restfulobjects/applib/src/main/java/org/apache/causeway/viewer/restfulobjects/applib/dtos/ScalarValueDtoV2.java
+++ b/viewers/restfulobjects/applib/src/main/java/org/apache/causeway/viewer/restfulobjects/applib/dtos/ScalarValueDtoV2.java
@@ -37,12 +37,12 @@ import lombok.NonNull;
@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 forNull(final @NonNull Class<?> type) {
+ return new ScalarValueDtoV2(typeName(type), null);
}
- public static ScalarValueDtoV2 forValue(@NonNull Object value) {
- return new ScalarValueDtoV2(value.getClass().getSimpleName(), value);
+ public static ScalarValueDtoV2 forValue(final @NonNull Object value) {
+ return new ScalarValueDtoV2(typeName(value.getClass()), value);
}
private String type;
@@ -53,4 +53,11 @@ public final class ScalarValueDtoV2 {
return value == null;
}
+ private static String typeName(final @NonNull Class<?> cls) {
+ return cls.isPrimitive()
+ || cls.getPackageName().startsWith("java.")
+ ? cls.getSimpleName()
+ : cls.getName();
+ }
+
}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ActionParameterListBuilder.java b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ActionParameterListBuilder.java
index 2cead49a9e..96a23e367c 100644
--- a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ActionParameterListBuilder.java
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ActionParameterListBuilder.java
@@ -24,6 +24,8 @@ import java.util.stream.Collectors;
import javax.ws.rs.client.Entity;
+import org.apache.causeway.applib.value.semantics.ValueDecomposition;
+
import lombok.Getter;
/**
@@ -86,6 +88,10 @@ public class ActionParameterListBuilder {
return this;
}
+ public ActionParameterListBuilder addActionParameter(final String parameterName, final ValueDecomposition decomposition) {
+ return addActionParameter(parameterName, decomposition.stringify());
+ }
+
//XXX would be nice to have, but also requires the RO spec to be updated
// public ActionParameterListBuilder addActionParameterDto(String parameterName, Object parameterDto) {
// actionParameters.put(parameterName, dto(parameterDto));
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigest.java b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigest.java
index 5c4ef3c978..37e4cc5d6d 100644
--- a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigest.java
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigest.java
@@ -179,6 +179,15 @@ class ResponseDigest<T> {
failureCause = _Exceptions.unrecoverable(e, "failed to read JAX-RS response content");
}
+ // guard against entity type mismatch
+ failureCause = entities.stream()
+ .filter(entity->!entityType.isAssignableFrom(entity.getClass()))
+ .map(entityOfWrongType->_Exceptions.unrecoverable("type mismatch when digesting REST response, expected: %s, got: %s",
+ entityType,
+ entityOfWrongType.getClass()))
+ .findAny()
+ .orElse(null);
+
return this;
}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient.java b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient.java
index a65ba69516..7ce1fcd15f 100644
--- a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient.java
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient.java
@@ -155,7 +155,7 @@ public class RestfulClient implements AutoCloseable {
public <T> Try<T> digest(final Response response, final Class<T> entityType) {
final var digest = ResponseDigest.wrap(response, entityType);
if(digest.isSuccess()) {
- return Try.success(digest.getEntity().orElse(null));
+ return Try.call(()->digest.getEntity().orElse(null));
}
return Try.failure(digest.getFailureCause());
}
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV2.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV2.java
index ec7b14abf1..d23b9d0585 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV2.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV2.java
@@ -20,6 +20,7 @@ package org.apache.causeway.viewer.restfulobjects.rendering.service.conneg;
import java.util.EnumSet;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Stream;
import javax.inject.Named;
@@ -28,12 +29,15 @@ import javax.ws.rs.core.Response;
import com.fasterxml.jackson.databind.node.POJONode;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.apache.causeway.applib.annotation.PriorityPrecedence;
import org.apache.causeway.applib.client.RepresentationTypeSimplifiedV2;
import org.apache.causeway.applib.client.SuppressionType;
+import org.apache.causeway.applib.value.semantics.ValueSemanticsProvider;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
import org.apache.causeway.core.metamodel.consent.Consent;
import org.apache.causeway.core.metamodel.interactions.managed.ManagedAction;
@@ -52,6 +56,7 @@ import org.apache.causeway.viewer.restfulobjects.rendering.Responses;
import org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation;
import org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.ObjectPropertyReprRenderer;
+import lombok.RequiredArgsConstructor;
import lombok.val;
/**
@@ -61,7 +66,9 @@ import lombok.val;
@Named(CausewayModuleViewerRestfulObjectsApplib.NAMESPACE + ".ContentNegotiationServiceOrgApacheCausewayV2")
@javax.annotation.Priority(PriorityPrecedence.MIDPOINT - 200)
@Qualifier("OrgApacheCausewayV2")
-public class ContentNegotiationServiceOrgApacheCausewayV2 extends ContentNegotiationServiceAbstract {
+@RequiredArgsConstructor(onConstructor_ = {@Autowired})
+public class ContentNegotiationServiceOrgApacheCausewayV2
+extends ContentNegotiationServiceAbstract {
/**
* Unlike RO v1.0, use a single content-type of <code>application/json;profile="urn:org.apache.causeway/v2"</code>.
@@ -72,11 +79,6 @@ public class ContentNegotiationServiceOrgApacheCausewayV2 extends ContentNegotia
private final ContentNegotiationServiceForRestfulObjectsV1_0 restfulObjectsV1_0;
- public ContentNegotiationServiceOrgApacheCausewayV2(final ContentNegotiationServiceForRestfulObjectsV1_0 restfulObjectsV1_0) {
- this.restfulObjectsV1_0 = restfulObjectsV1_0;
- }
-
-
/**
* Domain object is returned as a map with the RO 1.0 representation as a special '$$ro' property
* within that map.
@@ -263,10 +265,9 @@ public class ContentNegotiationServiceOrgApacheCausewayV2 extends ContentNegotia
objectAndActionInvocation.streamElementAdapters()
.map(elementAdapter->{
- val pojo = elementAdapter.getPojo();
- return pojo==null
- ? ScalarValueDtoV2.forNull(elementAdapter.getSpecification().getCorrespondingClass())
- : ScalarValueDtoV2.forValue(pojo);
+ val dto = dtoForValue(returnedAdapter)
+ .orElseGet(()->elementAdapter.getSpecification().getCorrespondingClass());
+ return dto;
})
.forEach(rootRepresentation::arrayAdd);
@@ -275,16 +276,14 @@ public class ContentNegotiationServiceOrgApacheCausewayV2 extends ContentNegotia
break;
case SCALAR_VALUE:
-
- val pojo = returnedAdapter.getPojo();
- if(pojo==null) {
+ val dto = dtoForValue(returnedAdapter).orElse(null);
+ if(dto==null) {
// 404 not found
return Responses.ofNotFound();
}
- val dto = ScalarValueDtoV2.forValue(pojo);
-
- rootRepresentation = new JsonRepresentation(new POJONode(dto));
+ val jsonNode = new POJONode(dto);
+ rootRepresentation = new JsonRepresentation(jsonNode);
headerContentType = RepresentationTypeSimplifiedV2.VALUE;
break;
@@ -305,6 +304,22 @@ public class ContentNegotiationServiceOrgApacheCausewayV2 extends ContentNegotia
return responseBuilder(responseBuilder);
}
+ private Optional<Object> dtoForValue(final @Nullable ManagedObject valueObject) {
+ if(ManagedObjects.isNullOrUnspecifiedOrEmpty(valueObject)
+ || !valueObject.getSpecification().isValue()) {
+ return Optional.empty();
+ }
+ val valSpec = valueObject.getSpecification();
+ val dto = valSpec.isCompositeValue()
+ ? ScalarValueDtoV2.forValue(
+ ((ValueSemanticsProvider)valSpec.valueFacetElseFail()
+ .selectDefaultSemantics().orElseThrow()) //TODO honor value semantics context?
+ .decompose(valueObject.getPojo())
+ .stringify())
+ : ScalarValueDtoV2.forValue(valueObject.getPojo());
+ return Optional.of(dto);
+ }
+
/**
* For easy subclassing to further customize, eg additional headers
*/
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java
index 432bf86479..f3ba178f42 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java
@@ -34,7 +34,9 @@ import org.springframework.util.ClassUtils;
import org.apache.causeway.applib.annotation.PriorityPrecedence;
import org.apache.causeway.applib.value.semantics.ValueDecomposition;
+import org.apache.causeway.applib.value.semantics.ValueSemanticsProvider;
import org.apache.causeway.commons.functional.Try;
+import org.apache.causeway.commons.internal.assertions._Assert;
import org.apache.causeway.commons.internal.base._Casts;
import org.apache.causeway.commons.internal.collections._Maps;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
@@ -91,6 +93,16 @@ public class JsonValueEncoderServiceDefault implements JsonValueEncoderService {
val valueSerializer =
Facets.valueSerializerElseFail(spec, valueClass);
+ // handle composite value types (requires a ValueSemanticsProvider for the valueClass to be registered with Spring)
+ if(spec.isCompositeValue()) {
+ _Assert.assertTrue(valueRepr.isString(), ()->"expected to receive a String originating from ValueDecomposition#stringify");
+ val valueFacet = spec.valueFacetElseFail();
+ val valSemantics = (ValueSemanticsProvider<?>)valueFacet.selectDefaultSemantics().orElseThrow();
+ val valDecomposition = ValueDecomposition.destringify(ValueType.COMPOSITE, valueRepr.asString());
+ val pojo = valSemantics.compose(valDecomposition);
+ return ManagedObject.value(spec, pojo);
+ }
+
final JsonValueConverter jsonValueConverter = converterByClass
.get(ClassUtils.resolvePrimitiveIfNecessary(valueClass));
if(jsonValueConverter == null) {
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/JsonParserHelper.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/JsonParserHelper.java
index 6c84e8e957..fb20cc1e92 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/JsonParserHelper.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/JsonParserHelper.java
@@ -103,10 +103,10 @@ public class JsonParserHelper {
if (objectSpec.isValue()) {
try {
return jsonValueEncoder.asAdapter(objectSpec, argValueRepr, null);
- }catch(IllegalArgumentException ex) {
+ } catch(IllegalArgumentException ex) {
argRepr.mapPutString("invalidReason", ex.getMessage());
throw ex;
- }catch(Exception ex) {
+ } catch(Exception ex) {
StringBuilder buf = new StringBuilder("Failed to parse representation ");
try {
final String reprStr = argRepr.getString("value");