diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java index 5b979216..a46b3d60 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.booking.item; import io.hypersistence.utils.hibernate.type.json.JsonType; import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType; import io.hypersistence.utils.hibernate.type.range.Range; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -10,31 +11,44 @@ import lombok.NoArgsConstructor; import lombok.Setter; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; -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.rbacobject.RbacObject; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; import org.hibernate.annotations.Type; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.Version; import java.io.IOException; import java.time.LocalDate; import java.util.Map; import java.util.TreeMap; import java.util.UUID; -import java.util.function.BinaryOperator; import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.joining; +import static net.hostsharing.hsadminng.mapper.PatchableMap.mapToString; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.*; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.UPDATE; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.ADMIN; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.AGENT; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.OWNER; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @@ -51,18 +65,9 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject { .withProp(e -> e.getDebitor().toShortString()) .withProp(e -> e.getValidity().asString()) .withProp(HsBookingItemEntity::getCaption) - .withProp(HsBookingItemEntity::getResourcesAsString) + .withProp(e -> mapToString(e.getResources())) .quotedValues(false); - private String getResourcesAsString() { - return "{ " + - ( - resources.keySet().stream().sorted() - .map(k -> k + ": " + resources.get(k))) - .collect(joining(", ") - ) + " }"; - } - @Id @GeneratedValue private UUID uuid; @@ -83,6 +88,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject { private String caption; @Builder.Default + @Setter(AccessLevel.NONE) @Type(JsonType.class) @Column(columnDefinition = "resources") private Map resources = new TreeMap<>(); @@ -103,7 +109,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject { return upperInclusiveFromPostgresDateRange(getValidity()); } - @Override public String toString() { return stringify.apply(this); @@ -115,17 +120,13 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject { ":" + caption; } - private static BinaryOperator thereIsOnlyOneValuePerKey(Object o, Object o1) { - return (a, b) -> a; - } - public static RbacView rbac() { return rbacViewFor("bookingItem", HsBookingItemEntity.class) .withIdentityView(SQL.query(""" - SELECT i.uuid as uuid, d.idName || ':' || i.caption as idName - FROM hs_booking_item i - JOIN hs_office_debitor_iv d ON d.uuid = i.debitorUuid - """)) + SELECT i.uuid as uuid, d.idName || ':' || i.caption as idName + FROM hs_booking_item i + JOIN hs_office_debitor_iv d ON d.uuid = i.debitorUuid + """)) .withRestrictedViewOrderBy(SQL.expression("validity")) .withUpdatableColumns("version", "validity", "resources") diff --git a/src/main/java/net/hostsharing/hsadminng/mapper/PatchableMap.java b/src/main/java/net/hostsharing/hsadminng/mapper/PatchableMap.java new file mode 100644 index 00000000..45669670 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/mapper/PatchableMap.java @@ -0,0 +1,42 @@ +package net.hostsharing.hsadminng.mapper; + +import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import jakarta.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +import static java.util.stream.Collectors.joining; + +public class PatchableMap { + + @SafeVarargs + public static Map patchableMap(final ImmutablePair... entries) { + final var map = new HashMap(); + Arrays.stream(entries).forEach(r -> map.put(r.getKey(), r.getValue())); + return map; + } + + @NotNull + public static ImmutablePair entry(final String key, final Object value) { + return new ImmutablePair<>(key, value); + } + + public static BiConsumer> assignMap = (HsBookingItemEntity i, Map r) -> { + i.getResources().clear(); + i.getResources().putAll(r); + }; + + public static String mapToString(final Map resources) { + return "{ " + + ( + resources.keySet().stream().sorted() + .map(k -> k + ": " + resources.get(k))) + .collect(joining(", ") + ) + + " }"; + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityPatcherUnitTest.java index c29edc4c..afc825e0 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityPatcherUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityPatcherUnitTest.java @@ -4,6 +4,7 @@ import io.hypersistence.utils.hibernate.type.range.Range; import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.ArbitraryBookingResourcesJsonResource; import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; +import net.hostsharing.hsadminng.mapper.PatchableMap; import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInstance; @@ -63,7 +64,7 @@ class HsBookingItemEntityPatcherUnitTest extends PatchUnitTestBase< final var entity = new HsBookingItemEntity(); entity.setUuid(INITIAL_BOOKING_ITEM_UUID); entity.setDebitor(TEST_DEBITOR); - entity.setResources(objectToMap(INITIAL_RESOURCES)); + entity.getResources().putAll(objectToMap(INITIAL_RESOURCES)); entity.setCaption(INITIAL_CAPTION); entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM)); return entity; @@ -91,7 +92,7 @@ class HsBookingItemEntityPatcherUnitTest extends PatchUnitTestBase< "resources", HsBookingItemPatchResource::setResources, PATCHED_RESOURCES, - HsBookingItemEntity::setResources, + PatchableMap.assignMap, objectToMap(PATCHED_RESOURCES)) .notNullable(), new JsonNullableProperty<>( diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java index ddf62936..e90b0cb6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java @@ -22,9 +22,9 @@ import jakarta.servlet.http.HttpServletRequest; import java.time.LocalDate; import java.util.Arrays; import java.util.List; -import java.util.Map; -import static java.util.Map.entry; +import static net.hostsharing.hsadminng.mapper.PatchableMap.entry; +import static net.hostsharing.hsadminng.mapper.PatchableMap.patchableMap; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted; @@ -186,18 +186,18 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup @Test public void hostsharingAdmin_canUpdateArbitraryBookingItem() { // given - final var givenBookingItem = givenSomeTemporaryBookingItem(1000111); + final var givenBookingItemUuid = givenSomeTemporaryBookingItem(1000111).getUuid(); // when final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - givenBookingItem.setResources(Map.ofEntries( - entry("CPUs", 2), - entry("SSD-storage", 512), - entry("HDD-storage", 2048))); - givenBookingItem.setValidity(Range.closedOpen( + final var foundBookingItem = em.find(HsBookingItemEntity.class, givenBookingItemUuid); + foundBookingItem.getResources().put("CPUs", 2); + foundBookingItem.getResources().remove("SSD-storage"); + foundBookingItem.getResources().put("HSD-storage", 2048); + foundBookingItem.setValidity(Range.closedOpen( LocalDate.parse("2019-05-17"), LocalDate.parse("2023-01-01"))); - return toCleanup(bookingItemRepo.save(givenBookingItem)); + return toCleanup(bookingItemRepo.save(foundBookingItem)); }); // then @@ -312,7 +312,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup .caption("some temp booking item") .validity(Range.closedOpen( LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01"))) - .resources(Map.ofEntries( + .resources(patchableMap( entry("CPUs", 1), entry("SSD-storage", 256))) .build();