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

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

commit 30b625b364b6686580421f6ac34c0945be608f4f
Merge: 7c7c8de477 0659fbb1ca
Author: Andi Huber <[email protected]>
AuthorDate: Tue Apr 9 06:47:58 2024 +0200

    Merge remote-tracking branch 'origin/master' into v3

 .../value/semantics/ValueSemanticsAbstract.java    |   1 -
 core/metamodel/src/main/java/module-info.java      |   1 +
 .../metamodel/CausewayModuleCoreMetamodel.java     |   2 +-
 .../valuesemantics/BlobValueSemantics.java         |  15 +-
 .../client/ActionParameterListBuilder.java         |  45 +++-
 .../restfulobjects/client/RestfulClient.java       |   2 +-
 .../JsonValueEncoderServiceDefault.java            |  33 ++-
 viewers/restfulobjects/test/pom.xml                | 271 ++++++++++----------
 .../restfulobjects/test/domain/dom/Staff.java      |  11 +
 ...IntegTest.can_create_staff_member.approved.json | 109 --------
 .../test/scenarios/staff/Staff_IntegTest.java      | 102 --------
 ...egTest.createStaffMemberWithPhoto.approved.json |  17 ++
 ...gTest.createStaffMemberWithPhoto2.approved.json |  17 ++
 ...teStaffMemberWithPhoto2_using_map.approved.json |  17 ++
 ...ateStaffMemberWithPhoto_using_map.approved.json |  17 ++
 .../scenarios/staff/Staff_hilevel_IntegTest.java   | 277 ++++++++++++++++++++
 ...gTest.createStaffMemberWithPhoto2.approved.json |  13 +
 .../staff/Staff_lowlevel_v1_IntegTest.java         | 280 +++++++++++++++++++++
 ...gTest.createStaffMemberWithPhoto2.approved.json |  17 ++
 .../staff/Staff_lowlevel_v2_IntegTest.java         | 181 +++++++++++++
 .../src/test/resources/junit-platform.propertes    |   2 +
 21 files changed, 1063 insertions(+), 367 deletions(-)

diff --cc 
core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/BlobValueSemantics.java
index a1a58fc94e,e128bb4002..84bf1f5345
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/BlobValueSemantics.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/valuesemantics/BlobValueSemantics.java
@@@ -20,10 -20,12 +20,12 @@@ package org.apache.causeway.core.metamo
  
  import java.util.function.UnaryOperator;
  
 -import javax.annotation.Priority;
 -import javax.inject.Named;
 +import jakarta.annotation.Priority;
 +import jakarta.inject.Named;
  
- import org.springframework.stereotype.Component;
+ import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+ import org.springframework.context.annotation.Bean;
+ import org.springframework.context.annotation.Configuration;
  
  import org.apache.causeway.applib.annotation.PriorityPrecedence;
  import org.apache.causeway.applib.util.schema.CommonDtoUtils;
diff --cc 
viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ActionParameterListBuilder.java
index 700b5a5820,21b69bb098..f9ffa579b4
--- 
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
@@@ -22,8 -22,10 +22,10 @@@ import java.util.LinkedHashMap
  import java.util.Map;
  import java.util.stream.Collectors;
  
 -import javax.ws.rs.client.Entity;
 +import jakarta.ws.rs.client.Entity;
  
+ import org.apache.causeway.applib.services.bookmark.Bookmark;
+ 
  import org.springframework.lang.Nullable;
  
  import org.apache.causeway.applib.util.schema.CommonDtoUtils;
diff --cc viewers/restfulobjects/test/pom.xml
index 6602899330,a6986d2ef6..ce4923c5cc
--- a/viewers/restfulobjects/test/pom.xml
+++ b/viewers/restfulobjects/test/pom.xml
@@@ -17,16 -17,18 +17,18 @@@
    specific language governing permissions and limitations
    under the License.
  -->
- <project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+ <project xmlns="http://maven.apache.org/POM/4.0.0";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
        <modelVersion>4.0.0</modelVersion>
  
 -      <parent>
 -              <groupId>org.apache.causeway.viewer</groupId>
 -              <artifactId>causeway-viewer-restfulobjects</artifactId>
 -              <version>2.0.0-SNAPSHOT</version>
 -      </parent>
 +    <parent>
 +        <groupId>org.apache.causeway.viewer</groupId>
 +        <artifactId>causeway-viewer-restfulobjects</artifactId>
 +        <version>3.0.0-SNAPSHOT</version>
 +    </parent>
  
-     <artifactId>causeway-viewer-restfulobjects-test</artifactId>
+       <artifactId>causeway-viewer-restfulobjects-test</artifactId>
  
        <name>Apache Causeway Viewer - RO (Test)</name>
  
diff --cc 
viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_hilevel_IntegTest.java
index 0000000000,b1e02c753f..e4b9c5cd8a
mode 000000,100644..100644
--- 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_hilevel_IntegTest.java
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_hilevel_IntegTest.java
@@@ -1,0 -1,278 +1,277 @@@
+ /*
+  *  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.
+  */
+ package org.apache.causeway.viewer.restfulobjects.test.scenarios.staff;
+ 
 -import javax.ws.rs.core.Response;
++import java.util.Map;
+ 
 -import org.apache.causeway.applib.services.bookmark.Bookmark;
++import jakarta.ws.rs.core.Response;
+ 
+ import org.approvaltests.Approvals;
+ import org.approvaltests.reporters.DiffReporter;
+ import org.approvaltests.reporters.UseReporter;
+ import org.junit.jupiter.api.Test;
+ 
+ import static org.assertj.core.api.Assertions.assertThat;
+ 
+ import org.springframework.transaction.annotation.Propagation;
+ 
++import org.apache.causeway.applib.services.bookmark.Bookmark;
+ import org.apache.causeway.applib.value.Blob;
+ import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
+ import org.apache.causeway.commons.io.DataSource;
+ import org.apache.causeway.viewer.restfulobjects.test.domain.dom.Department;
+ import 
org.apache.causeway.viewer.restfulobjects.test.scenarios.Abstract_IntegTest;
+ 
+ import lombok.SneakyThrows;
+ import lombok.val;
+ 
 -import java.util.Map;
 -
+ class Staff_hilevel_IntegTest extends Abstract_IntegTest {
+ 
+     @SneakyThrows
+     @Test
+     @UseReporter(DiffReporter.class)
+     public void createStaffMemberWithPhoto() {
+ 
+         // given
+         final var staffName = "Fred Smith";
+ 
+         final var bookmarkBeforeIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+ 
+         assertThat(bookmarkBeforeIfAny).isEmpty();
+ 
+         final Blob photo = readFileAsBlob("StaffMember-photo-Bar.pdf");
+         final var requestBuilder = 
restfulClient.request("services/university.dept.Staff/actions/createStaffMemberWithPhoto/invoke");
+ 
+         /*
+          *  String name,
+          *  Department.SecondaryKey departmentSecondaryKey,
+          *  Blob photo
+          */
+         var args = restfulClient.arguments()
+                 .addActionParameter("name", staffName)
+                 .addActionParameter("departmentSecondaryKey", 
Department.SecondaryKey.class, new Department.SecondaryKey("Classics"))
+                 .addActionParameter("photo", photo)
+                 .build();
+ 
+         Approvals.verify(args.getEntity(), jsonOptions());
+ 
+         // when
+         val response = requestBuilder.post(args);
+ 
+         // then
+         
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
+         
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
+ 
+         // and also json response
+         val entity = response.readEntity(String.class);
+         assertThat(response)
+                 .extracting(Response::getStatus)
+                 .isEqualTo(Response.Status.OK.getStatusCode());
+ 
+         // and also object is created in database
+         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+         assertThat(bookmarkAfterIfAny).isNotEmpty();
+     }
+ 
+     @SneakyThrows
+     @Test
+     @UseReporter(DiffReporter.class)
+     public void createStaffMemberWithPhoto_using_map() {
+ 
+         // given
+         final var staffName = "Fred Smith";
+ 
+         final var bookmarkBeforeIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+ 
+         assertThat(bookmarkBeforeIfAny).isEmpty();
+ 
+         final Blob photo = readFileAsBlob("StaffMember-photo-Bar.pdf");
+         final var requestBuilder = 
restfulClient.request("services/university.dept.Staff/actions/createStaffMemberWithPhoto/invoke");
+ 
+         var args = restfulClient.arguments()
+                 .addActionParameter("name", staffName)
+                 .addActionParameter("departmentSecondaryKey", Map.of("name", 
"Classics"))
+                 .addActionParameter("photo", photo)
+                 .build();
+ 
+         Approvals.verify(args.getEntity(), jsonOptions());
+ 
+         // when
+         val response = requestBuilder.post(args);
+ 
+         // then
+         
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
+         
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
+ 
+         // and also json response
+         val entity = response.readEntity(String.class);
+         assertThat(response)
+                 .extracting(Response::getStatus)
+                 .isEqualTo(Response.Status.OK.getStatusCode());
+ 
+         // and also object is created in database
+         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+         assertThat(bookmarkAfterIfAny).isNotEmpty();
+     }
+ 
+     @SneakyThrows
+     @Test
+     @UseReporter(DiffReporter.class)
+     public void createStaffMemberWithPhoto2() {
+ 
+         // given
+         final var staffName = "Fred Smith";
+ 
+         final var bookmarkBeforeIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+ 
+         assertThat(bookmarkBeforeIfAny).isEmpty();
+ 
+         // and given
+         final var departmentName = "Classics";
+         final var departmentBookmark = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
departmentRepository.findByName(departmentName);
+             return bookmarkService.bookmarkFor(staffMember).orElseThrow();
+         }).valueAsNonNullElseFail();
+ 
+         // and given
+         final Blob photo = readFileAsBlob("StaffMember-photo-Bar.pdf");
+         final var requestBuilder = 
restfulClient.request("services/university.dept.Staff/actions/createStaffMemberWithPhoto2/invoke");
+ 
+         var args = restfulClient.arguments()
+                 .addActionParameter("name", staffName)
+                 .addActionParameter("department", departmentBookmark)
+                 .addActionParameter("photo", photo)
+                 .build();
+ 
+         Approvals.verify(args.getEntity(), jsonOptions());
+ 
+         // when
+         val response = requestBuilder.post(args);
+ 
+         // then
+         
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
+         
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
+ 
+         // and also json response
+         val entity = response.readEntity(String.class);
+         assertThat(response)
+                 .extracting(Response::getStatus)
+                 .isEqualTo(Response.Status.OK.getStatusCode());
+ 
+         // and also object is created in database
+         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+         assertThat(bookmarkAfterIfAny).isNotEmpty();
+     }
+ 
+     @SneakyThrows
+     @Test
+     @UseReporter(DiffReporter.class)
+     public void createStaffMemberWithPhoto2_using_map() {
+ 
+         // given
+         final var staffName = "Fred Smith";
+ 
+         final var bookmarkBeforeIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+ 
+         assertThat(bookmarkBeforeIfAny).isEmpty();
+ 
+         // and given
+         final var departmentName = "Classics";
+         final var departmentBookmark = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
departmentRepository.findByName(departmentName);
+             return bookmarkService.bookmarkFor(staffMember).orElseThrow();
+         }).valueAsNonNullElseFail();
+ 
+         // and given
+         final Blob photo = readFileAsBlob("StaffMember-photo-Bar.pdf");
+         final var requestBuilder = 
restfulClient.request("services/university.dept.Staff/actions/createStaffMemberWithPhoto2/invoke");
+ 
+         /*
+          *  String name,
+          *  Department.SecondaryKey departmentSecondaryKey,
+          *  Blob photo
+          */
+         var args = restfulClient.arguments()
+                 .addActionParameter("name", staffName)
+                 .addActionParameter("department", Map.of("href", 
asAbsoluteHref(departmentBookmark)))
+                 .addActionParameter("photo", photo)
+                 .build();
+ 
+         Approvals.verify(args.getEntity(), jsonOptions());
+ 
+         // when
+         val response = requestBuilder.post(args);
+ 
+         // then
+         
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
+         
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
+ 
+         // and also json response
+         val entity = response.readEntity(String.class);
+         assertThat(response)
+                 .extracting(Response::getStatus)
+                 .isEqualTo(Response.Status.OK.getStatusCode());
+ 
+         // and also object is created in database
+         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+         assertThat(bookmarkAfterIfAny).isNotEmpty();
+     }
+ 
+ 
+     private Blob readFileAsBlob(final String fileName) {
+         var bytes = DataSource.ofResource(Abstract_IntegTest.class, fileName)
+                 .bytes();
+         return Blob.of(fileName, CommonMimeType.PDF, bytes);
+     }
+ 
 -    private String asAbsoluteHref(Bookmark bookmark) {
++    private String asAbsoluteHref(final Bookmark bookmark) {
+         return String.format("%s%s", 
restfulClient.getConfig().getRestfulBaseUrl(), asRelativeHref(bookmark));
+     }
+ 
 -    private String asRelativeHref(Bookmark bookmark) {
++    private String asRelativeHref(final Bookmark bookmark) {
+         return String.format("objects/%s/%s", bookmark.getLogicalTypeName(), 
bookmark.getIdentifier());
+     }
+ 
+ 
+ }
+ 
diff --cc 
viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v1_IntegTest.java
index 0000000000,399178c91f..485bcff32c
mode 000000,100644..100644
--- 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v1_IntegTest.java
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v1_IntegTest.java
@@@ -1,0 -1,279 +1,280 @@@
+ /*
+  *  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.
+  */
+ package org.apache.causeway.viewer.restfulobjects.test.scenarios.staff;
+ 
+ import java.io.IOException;
+ import java.net.URISyntaxException;
+ import java.nio.charset.StandardCharsets;
+ import java.util.Base64;
+ 
 -import javax.activation.MimeType;
 -import javax.activation.MimeTypeParseException;
 -import javax.annotation.Priority;
 -import javax.inject.Named;
 -import javax.ws.rs.client.Entity;
 -import javax.ws.rs.client.Invocation;
 -import javax.ws.rs.core.Response;
 -
 -import com.google.common.io.Resources;
++import jakarta.activation.MimeType;
++import jakarta.activation.MimeTypeParseException;
++import jakarta.annotation.Priority;
++import jakarta.inject.Named;
++import jakarta.ws.rs.client.Entity;
++import jakarta.ws.rs.client.Invocation;
++import jakarta.ws.rs.core.Response;
++
+ import com.google.gson.GsonBuilder;
+ 
+ import org.approvaltests.Approvals;
+ import org.approvaltests.reporters.DiffReporter;
+ import org.approvaltests.reporters.UseReporter;
+ import org.junit.jupiter.api.BeforeEach;
+ import org.junit.jupiter.api.Order;
+ import org.junit.jupiter.api.Test;
+ 
+ import static org.assertj.core.api.Assertions.assertThat;
+ 
+ import org.springframework.context.annotation.Import;
+ import org.springframework.stereotype.Component;
+ import org.springframework.test.annotation.DirtiesContext;
+ import org.springframework.transaction.annotation.Propagation;
+ 
+ import org.apache.causeway.applib.annotation.PriorityPrecedence;
+ import org.apache.causeway.applib.services.bookmark.Bookmark;
+ import org.apache.causeway.applib.value.Blob;
+ import org.apache.causeway.applib.value.NamedWithMimeType;
+ import org.apache.causeway.applib.value.semantics.Renderer;
+ import org.apache.causeway.applib.value.semantics.ValueDecomposition;
+ import org.apache.causeway.applib.value.semantics.ValueSemanticsProvider;
+ import org.apache.causeway.commons.collections.Can;
+ import org.apache.causeway.commons.internal.base._Bytes;
+ import org.apache.causeway.commons.internal.base._Strings;
++import org.apache.causeway.commons.io.DataSource;
+ import org.apache.causeway.core.metamodel.valuesemantics.BlobValueSemantics;
+ import org.apache.causeway.schema.common.v2.ValueType;
+ import 
org.apache.causeway.viewer.restfulobjects.test.scenarios.Abstract_IntegTest;
+ 
+ import lombok.Getter;
+ import lombok.SneakyThrows;
+ import lombok.val;
+ 
+ 
+ @Order(value = Integer.MAX_VALUE)   // last
+ @DirtiesContext
+ 
@Import({Staff_lowlevel_v1_IntegTest.BlobValueSemanticsV1LegacyEncoding.class})
+ public class Staff_lowlevel_v1_IntegTest extends Abstract_IntegTest {
+ 
+     private GsonBuilder gsonBuilder;
+ 
+     @BeforeEach
+     void setup() {
+         gsonBuilder = new GsonBuilder();
+     }
+ 
+     @SneakyThrows
+     @Test
+     @UseReporter(DiffReporter.class)
+     public void createStaffMemberWithPhoto2() {
+ 
+         // given
+         final var staffName = "Fred Smith";
+ 
+         final var bookmarkBeforeIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+ 
+         assertThat(bookmarkBeforeIfAny).isEmpty();
+ 
+         // and given
+         final var departmentName = "Classics";
+         final var departmentBookmark = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
departmentRepository.findByName(departmentName);
+             return bookmarkService.bookmarkFor(staffMember).orElseThrow();
+         }).valueAsNonNullElseFail();
+ 
+         String departmentHref = asRelativeHref(departmentBookmark);
+         Invocation.Builder departmentRequest = 
restfulClient.request(departmentHref);
+         Response departmentResponse = departmentRequest.get();
+         
assertThat(departmentResponse.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
+ 
+         // and given
+         final var photoEncoded = 
readFileAndEncodeAsBlob("StaffMember-photo-Bar.pdf");
+ 
+         // when create request
+         final var requestBuilder = 
restfulClient.request("services/university.dept.Staff/actions/createStaffMemberWithPhoto2/invoke");
+ 
+         final var body = new Body(staffName, 
asAbsoluteHref(departmentBookmark), photoEncoded);
+         final var bodyJson = gsonBuilder.create().toJson(body);
+ 
+         // then
+         Approvals.verify(bodyJson, jsonOptions());
+ 
+         // and when send request
+         val response = requestBuilder.post(Entity.entity(bodyJson, 
"application/json"));
+ 
+         // then
+         val entity = response.readEntity(String.class);
+         
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
+         
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
+ 
+         // and also json response
+ 
+         // and also object is created in database
+         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+         assertThat(bookmarkAfterIfAny).isNotEmpty();
+     }
+ 
+     private String asAbsoluteHref(final Bookmark bookmark) {
+         return String.format("%s%s", 
restfulClient.getConfig().getRestfulBaseUrl(), asRelativeHref(bookmark));
+     }
+ 
+     private String asRelativeHref(final Bookmark bookmark) {
+         return String.format("objects/%s/%s", bookmark.getLogicalTypeName(), 
bookmark.getIdentifier());
+     }
+ 
+     private String readFileAndEncodeAsBlob(final String fileName) throws 
IOException, URISyntaxException {
 -        byte[] bytes = 
Resources.toByteArray(Resources.getResource(Abstract_IntegTest.class, 
fileName));
++        var bytes = DataSource.ofResource(Abstract_IntegTest.class, fileName)
++                .bytes();
+         String photoEncoded = encodePdf(fileName, bytes);
+         return photoEncoded;
+     }
+ 
+     private String encodePdf(final String fileName, final byte[] pdfBytes) 
throws URISyntaxException {
+         final String pdfBytesEncoded = 
Base64.getEncoder().encodeToString(pdfBytes);
+         final String encodedBlob = String.format("%s:%s:%s", fileName, 
"application/pdf", pdfBytesEncoded);
+         return encodedBlob;
+     }
+ 
+     @Getter
+     static class Body {
+ 
+         /**
+          * @param nameValue
+          * @param departmentHrefValue
+          * @param blobValue - is the Blob encoded format: 
"filename.pdf:application/pdf:pdfBytesBase64Encoded"
+          */
+         Body(final String nameValue, final String departmentHrefValue, final 
String blobValue) {
+             photo = new Blob(blobValue);
+             name = new Name(nameValue);
+             department = new Department(new 
Department.Value(departmentHrefValue));
+         }
+ 
+         private Name name;
+         private Department department;
+         private Blob photo;
+ 
+         @lombok.Value
+         static class Name {
+             private String value;
+         }
+ 
+         @lombok.Value
+         static class Department {
+             private Value value;
+ 
+             @lombok.Value
+             static class Value {
+                 private String href;
+             }
+ 
+         }
+ 
+         @lombok.Value
+         static class Blob {
+             private String value;
+         }
+     }
+ 
+     @Component
+     @Named("causeway.metamodel.value.BlobValueSemantics")
+     @Priority(PriorityPrecedence.EARLY)
+     public static class BlobValueSemanticsV1LegacyEncoding
+             extends BlobValueSemantics
+             implements
+             Renderer<Blob> {
+ 
+         public BlobValueSemanticsV1LegacyEncoding(){
+         }
+ 
+         @Override
+         public Class<Blob> getCorrespondingClass() {
+             return Blob.class;
+         }
+ 
+         @Override
+         public ValueType getSchemaValueType() {
+             return ValueType.STRING;
+         }
+ 
+         // -- COMPOSER
+ 
+         @Override
+         public ValueDecomposition decompose(final Blob value) {
+             return decomposeAsString(value, this::toEncodedString, () -> 
null);
+         }
+ 
+         @Override
+         public Blob compose(final ValueDecomposition decomposition) {
+             return composeFromString(decomposition, this::fromEncodedString, 
()->null);
+         }
+ 
+         // RENDERER
+ 
+         @Override
+         public String titlePresentation(final ValueSemanticsProvider.Context 
context, final Blob value) {
+             return renderTitle(value, Blob::getName);
+         }
+ 
+         @Override
+         public String htmlPresentation(final ValueSemanticsProvider.Context 
context, final Blob value) {
+             return renderHtml(value, Blob::getName);
+         }
+ 
+         private String toEncodedString(final Blob blob) {
+             return blob.getName() + ":" + blob.getMimeType().getBaseType() + 
":" +
+             _Strings.ofBytes(_Bytes.encodeToBase64(Base64.getEncoder(), 
blob.getBytes()), StandardCharsets.UTF_8);
+         }
+ 
+         private Blob fromEncodedString(final String data) {
+             final int colonIdx = data.indexOf(':');
+             final String name  = data.substring(0, colonIdx);
+             final int colon2Idx  = data.indexOf(":", colonIdx+1);
+             final String mimeTypeBase = data.substring(colonIdx+1, colon2Idx);
+             final String payload = data.substring(colon2Idx+1);
+             final byte[] bytes = _Bytes.decodeBase64(Base64.getDecoder(), 
payload.getBytes(StandardCharsets.UTF_8));
+             try {
+                 return new Blob(name, new MimeType(mimeTypeBase), bytes);
+             } catch (MimeTypeParseException e) {
+                 throw new RuntimeException(e);
+             }
+         }
+ 
+         // -- EXAMPLES
+ 
+         @Override
+         public Can<Blob> getExamples() {
+             return Can.of(
+                     Blob.of("a Blob", NamedWithMimeType.CommonMimeType.BIN, 
new byte[] {1, 2, 3}),
+                     Blob.of("another Blob", 
NamedWithMimeType.CommonMimeType.BIN, new byte[] {3, 4}));
+         }
+ 
+     }
+ 
+ 
+ }
+ 
+ 
+ 
+ 
diff --cc 
viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v2_IntegTest.java
index 0000000000,26b6eeb8a1..146a186e49
mode 000000,100644..100644
--- 
a/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v2_IntegTest.java
+++ 
b/viewers/restfulobjects/test/src/test/java/org/apache/causeway/viewer/restfulobjects/test/scenarios/staff/Staff_lowlevel_v2_IntegTest.java
@@@ -1,0 -1,181 +1,181 @@@
+ /*
+  *  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.
+  */
+ package org.apache.causeway.viewer.restfulobjects.test.scenarios.staff;
+ 
+ import java.io.IOException;
+ import java.util.Base64;
+ 
 -import javax.ws.rs.client.Entity;
 -import javax.ws.rs.client.Invocation;
 -import javax.ws.rs.core.Response;
++import jakarta.ws.rs.client.Entity;
++import jakarta.ws.rs.client.Invocation;
++import jakarta.ws.rs.core.Response;
+ 
 -import com.google.common.io.Resources;
+ import com.google.gson.GsonBuilder;
+ 
 -import org.apache.causeway.applib.value.Blob;
 -
+ import org.approvaltests.Approvals;
+ import org.approvaltests.reporters.DiffReporter;
+ import org.approvaltests.reporters.UseReporter;
+ import org.junit.jupiter.api.BeforeEach;
+ import org.junit.jupiter.api.Test;
+ 
+ import static org.assertj.core.api.Assertions.assertThat;
+ 
+ import org.springframework.transaction.annotation.Propagation;
+ 
+ import org.apache.causeway.applib.services.bookmark.Bookmark;
++import org.apache.causeway.applib.value.Blob;
++import org.apache.causeway.commons.io.DataSource;
+ import 
org.apache.causeway.viewer.restfulobjects.test.scenarios.Abstract_IntegTest;
+ 
+ import lombok.Getter;
+ import lombok.SneakyThrows;
+ import lombok.val;
+ 
+ public class Staff_lowlevel_v2_IntegTest extends Abstract_IntegTest {
+ 
+     private GsonBuilder gsonBuilder;
+ 
+     @BeforeEach
+     void setup() {
+         gsonBuilder = new GsonBuilder();
+     }
+ 
+     @SneakyThrows
+     @Test
+     @UseReporter(DiffReporter.class)
+     public void createStaffMemberWithPhoto2() {
+ 
+         // given
+         final var staffName = "Fred Smith";
+ 
+         final var bookmarkBeforeIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+ 
+         assertThat(bookmarkBeforeIfAny).isEmpty();
+ 
+         // and given
+         final var departmentName = "Classics";
+         final var departmentBookmark = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
departmentRepository.findByName(departmentName);
+             return bookmarkService.bookmarkFor(staffMember).orElseThrow();
+         }).valueAsNonNullElseFail();
+ 
+         String departmentHref = asRelativeHref(departmentBookmark);
+         Invocation.Builder departmentRequest = 
restfulClient.request(departmentHref);
+         Response departmentResponse = departmentRequest.get();
+         
assertThat(departmentResponse.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
+ 
+         // and given
+         final var photoEncoded = readFileAsBlob("StaffMember-photo-Bar.pdf");
+ 
+         // when create request
+         final var requestBuilder = 
restfulClient.request("services/university.dept.Staff/actions/createStaffMemberWithPhoto2/invoke");
+ 
+         final var body = new Body(staffName, 
asAbsoluteHref(departmentBookmark), photoEncoded);
+         final var bodyJson = gsonBuilder.create().toJson(body);
+ 
+         // then
+         Approvals.verify(bodyJson, jsonOptions());
+ 
+         // and when send request
+         val response = requestBuilder.post(Entity.entity(bodyJson, 
"application/json"));
+ 
+         // then
+         val entity = response.readEntity(String.class);
+         
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Response.Status.Family.SUCCESSFUL);
+         
assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
+ 
+         // and also json response
+ 
+         // and also object is created in database
+         final var bookmarkAfterIfAny = 
transactionService.callTransactional(Propagation.REQUIRED, () -> {
+             final var staffMember = 
staffMemberRepository.findByName(staffName);
+             return bookmarkService.bookmarkFor(staffMember);
+         }).valueAsNonNullElseFail();
+         assertThat(bookmarkAfterIfAny).isNotEmpty();
+     }
+ 
 -    private String asAbsoluteHref(Bookmark bookmark) {
++    private String asAbsoluteHref(final Bookmark bookmark) {
+         return String.format("%s%s", 
restfulClient.getConfig().getRestfulBaseUrl(), asRelativeHref(bookmark));
+     }
+ 
 -    private String asRelativeHref(Bookmark bookmark) {
++    private String asRelativeHref(final Bookmark bookmark) {
+         return String.format("objects/%s/%s", bookmark.getLogicalTypeName(), 
bookmark.getIdentifier());
+     }
+ 
 -    private Blob readFileAsBlob(String fileName) throws IOException {
 -        byte[] bytes = 
Resources.toByteArray(Resources.getResource(Abstract_IntegTest.class, 
fileName));
++    private Blob readFileAsBlob(final String fileName) throws IOException {
++        var bytes = DataSource.ofResource(Abstract_IntegTest.class, fileName)
++                .bytes();
+         return new Blob(fileName, "application/pdf", bytes);
+     }
+ 
+ 
+     @Getter
+     static class Body {
+ 
+         /**
+          * @param nameValue
+          * @param departmentHrefValue
+          * @param blob - is the Blob to be formatted
+          */
 -        Body(String nameValue, String departmentHrefValue, 
org.apache.causeway.applib.value.Blob blob) {
++        Body(final String nameValue, final String departmentHrefValue, final 
org.apache.causeway.applib.value.Blob blob) {
+             name = new Name(nameValue);
+             department = new Department(new 
Department.Value(departmentHrefValue));
+             photo = new Blob(new Blob.Value(blob.getName(), 
blob.getMimeType().toString(), 
Base64.getEncoder().encodeToString(blob.getBytes())));
+         }
+ 
+         private Name name;
+         private Department department;
+         private Blob photo;
+ 
+         @lombok.Value
+         static class Name {
+             private String value;
+         }
+ 
+         @lombok.Value
+         static class Department {
+             private Value value;
+ 
+             @lombok.Value
+             static class Value {
+                 private String href;
+             }
+ 
+         }
+ 
+ 
+         @lombok.Value
+         static class Blob {
+             private Value value;
+ 
+             @lombok.Value
+             static class Value {
+                 private String name;
+                 private String mimeType;
+                 private String bytes;
+             }
+         }
+ 
+     }
+ 
+ }
+ 
+ 


Reply via email to