1
0

spring-boot-3-2-upgrade (#32)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/32
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
Michael Hoennig
2024-04-02 13:24:25 +02:00
parent ad04faa21d
commit 73c378b456
25 changed files with 62 additions and 198 deletions

View File

@ -15,11 +15,9 @@ import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.function.Predicate.not;
import static net.hostsharing.hsadminng.mapper.PostgresArray.fromPostgresArray;
import static org.springframework.transaction.annotation.Propagation.MANDATORY;
@Service
@ -82,14 +80,11 @@ public class Context {
}
public String[] getAssumedRoles() {
final byte[] result = (byte[]) em.createNativeQuery("select assumedRoles() as roles", String[].class).getSingleResult();
return fromPostgresArray(result, String.class, Function.identity());
return (String[]) em.createNativeQuery("select assumedRoles() as roles", String[].class).getSingleResult();
}
public UUID[] currentSubjectsUuids() {
final byte[] result = (byte[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class)
.getSingleResult();
return fromPostgresArray(result, UUID.class, UUID::fromString);
return (UUID[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class).getSingleResult();
}
public static String getCallerMethodNameFromStackFrame(final int skipFrames) {

View File

@ -11,16 +11,18 @@ import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.lang.Nullable;
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.validation.FieldError;
import org.springframework.validation.method.ParameterValidationResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.ValidationException;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.*;
import java.util.regex.Pattern;
import static net.hostsharing.hsadminng.errors.CustomErrorResponse.*;
@ -119,6 +121,28 @@ public class RestResponseEntityExceptionHandler
return errorResponse(request, HttpStatus.BAD_REQUEST, errorList.toString());
}
@SuppressWarnings("unchecked,rawtypes")
@Override
protected ResponseEntity handleHandlerMethodValidationException(
final HandlerMethodValidationException exc,
final HttpHeaders headers,
final HttpStatusCode status,
final WebRequest request) {
final var errorList = exc
.getAllValidationResults()
.stream()
.map(ParameterValidationResult::getResolvableErrors)
.flatMap(Collection::stream)
.filter(FieldError.class::isInstance)
.map(FieldError.class::cast)
.map(fieldError -> fieldError.getField() + " " + fieldError.getDefaultMessage() + " but is \""
+ fieldError.getRejectedValue() + "\"")
.toList();
return errorResponse(request, HttpStatus.BAD_REQUEST, errorList.toString());
}
private String userReadableEntityClassName(final String exceptionMessage) {
final var regex = "(net.hostsharing.hsadminng.[a-z0-9_.]*.[A-Za-z0-9_$]*Entity) ";
final var pattern = Pattern.compile(regex);

View File

@ -13,7 +13,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
import java.time.LocalDate;
import java.util.ArrayList;
@ -59,7 +58,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
public ResponseEntity<HsOfficeCoopAssetsTransactionResource> addCoopAssetsTransaction(
final String currentUser,
final String assumedRoles,
@Valid final HsOfficeCoopAssetsTransactionInsertResource requestBody) {
final HsOfficeCoopAssetsTransactionInsertResource requestBody) {
context.define(currentUser, assumedRoles);
validate(requestBody);

View File

@ -10,7 +10,7 @@ import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator;

View File

@ -13,7 +13,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
import java.time.LocalDate;
import java.util.ArrayList;
@ -60,7 +59,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
public ResponseEntity<HsOfficeCoopSharesTransactionResource> addCoopSharesTransaction(
final String currentUser,
final String assumedRoles,
@Valid final HsOfficeCoopSharesTransactionInsertResource requestBody) {
final HsOfficeCoopSharesTransactionInsertResource requestBody) {
context.define(currentUser, assumedRoles);
validate(requestBody);

View File

@ -7,10 +7,8 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@ -12,7 +12,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.validation.Valid;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
@ -53,7 +52,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
public ResponseEntity<HsOfficeMembershipResource> addMembership(
final String currentUser,
final String assumedRoles,
@Valid final HsOfficeMembershipInsertResource body) {
final HsOfficeMembershipInsertResource body) {
context.define(currentUser, assumedRoles);

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
import io.hypersistence.utils.hibernate.type.range.Range;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;

View File

@ -14,7 +14,6 @@ import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBui
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.validation.Valid;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
@ -57,7 +56,7 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
public ResponseEntity<HsOfficeSepaMandateResource> addSepaMandate(
final String currentUser,
final String assumedRoles,
@Valid final HsOfficeSepaMandateInsertResource body) {
final HsOfficeSepaMandateInsertResource body) {
context.define(currentUser, assumedRoles);

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.office.sepamandate;
import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
import io.hypersistence.utils.hibernate.type.range.Range;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;

View File

@ -1,58 +0,0 @@
package net.hostsharing.hsadminng.mapper;
import lombok.experimental.UtilityClass;
import org.postgresql.util.PGtokenizer;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
@UtilityClass
public class PostgresArray {
/**
* Converts a byte[], as returned for a Postgres-array by native queries, to a Java array.
*
* <p>This example code worked with Hibernate 5 (Spring Boot 3.0.x):
* <pre><code>
* return (UUID[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class).getSingleResult();
* </code></pre>
* </p>
*
* <p>With Hibernate 6 (Spring Boot 3.1.x), this utility method can be used like such:
* <pre><code>
* final byte[] result = (byte[]) em.createNativeQuery("select * from currentSubjectsUuids() as uuids", UUID[].class)
* .getSingleResult();
* return fromPostgresArray(result, UUID.class, UUID::fromString);
* </code></pre>
* </p>
*
* @param pgArray the byte[] returned by a native query containing as rendered for a Postgres array
* @param elementClass the class of a single element of the Java array to be returned
* @param itemParser converts a string element to the specified elementClass
* @return a Java array containing the data from pgArray
* @param <T> type of a single element of the Java array
*/
public static <T> T[] fromPostgresArray(final byte[] pgArray, final Class<T> elementClass, final Function<String, T> itemParser) {
final var pgArrayLiteral = new String(pgArray, StandardCharsets.UTF_8);
if (pgArrayLiteral.length() == 2) {
return newGenericArray(elementClass, 0);
}
final PGtokenizer tokenizer = new PGtokenizer(pgArrayLiteral.substring(1, pgArrayLiteral.length()-1), ',');
tokenizer.remove("\"", "\"");
final T[] array = newGenericArray(elementClass, tokenizer.getSize()); // Create a new array of the specified type and length
for ( int n = 0; n < tokenizer.getSize(); ++n ) {
final String token = tokenizer.getToken(n);
if ( !"NULL".equals(token) ) {
array[n] = itemParser.apply(token.trim().replace("\\\"", "\""));
}
}
return array;
}
@SuppressWarnings("unchecked")
private static <T> T[] newGenericArray(final Class<T> elementClass, final int length) {
return (T[]) Array.newInstance(elementClass, length);
}
}

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.mapper;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.Range;
import lombok.experimental.UtilityClass;
import java.time.LocalDate;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.Range;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeReasonForTerminationResource;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import org.junit.jupiter.api.Test;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.Range;
import java.time.LocalDate;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.sepamandate;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.Range;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.sepamandate;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource;
import net.hostsharing.test.PatchUnitTestBase;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.sepamandate;
import com.vladmihalcea.hibernate.type.range.Range;
import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;

View File

@ -7,7 +7,6 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import jakarta.persistence.EntityManager;
import java.util.UUID;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat;
@ -30,9 +29,7 @@ class PostgresArrayIntegrationTest {
return emptyArray;
end; $$;
""").executeUpdate();
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnEmptyArray()", String[].class).getSingleResult();
final String[] result = PostgresArray.fromPostgresArray(pgArray, String.class, Function.identity());
final String[] result = (String[]) em.createNativeQuery("SELECT returnEmptyArray()", String[].class).getSingleResult();
assertThat(result).isEmpty();
}
@ -53,9 +50,7 @@ class PostgresArrayIntegrationTest {
return array[text1, text2, text3, null, text4];
end; $$;
""").executeUpdate();
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnStringArray()", String[].class).getSingleResult();
final String[] result = PostgresArray.fromPostgresArray(pgArray, String.class, Function.identity());
final String[] result = (String[]) em.createNativeQuery("SELECT returnStringArray()", String[].class).getSingleResult();
assertThat(result).containsExactly("one", "two, three", "four; five", null, "say \"Hello\" to me");
}
@ -75,9 +70,7 @@ class PostgresArrayIntegrationTest {
return ARRAY[uuid1, uuid2, null, uuid3];
end; $$;
""").executeUpdate();
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnUuidArray()", UUID[].class).getSingleResult();
final UUID[] result = PostgresArray.fromPostgresArray(pgArray, UUID.class, UUID::fromString);
final UUID[] result = (UUID[]) em.createNativeQuery("SELECT returnUuidArray()", UUID[].class).getSingleResult();
assertThat(result).containsExactly(
UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"),