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