diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java index 7edb581b2..d921f4b5b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java @@ -9,15 +9,22 @@ */ package org.eclipse.hawkbit.repository.jpa.model; +import java.io.Serial; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; +import jakarta.persistence.AttributeConverter; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.ConstraintMode; +import jakarta.persistence.Convert; +import jakarta.persistence.Converter; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.ForeignKey; @@ -45,28 +52,30 @@ import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; -import org.eclipse.persistence.annotations.ConversionValue; -import org.eclipse.persistence.annotations.Convert; -import org.eclipse.persistence.annotations.ObjectTypeConverter; /** * JPA implementation of {@link Action}. */ -@Table(name = "sp_action", indexes = { @Index(name = "sp_idx_action_01", columnList = "tenant,distribution_set"), - @Index(name = "sp_idx_action_02", columnList = "tenant,target,active"), - @Index(name = "sp_idx_action_prim", columnList = "tenant,id") }) +@Table( + name = "sp_action", + indexes = { + @Index(name = "sp_idx_action_01", columnList = "tenant,distribution_set"), + @Index(name = "sp_idx_action_02", columnList = "tenant,target,active"), + @Index(name = "sp_idx_action_prim", columnList = "tenant,id") + }) @NamedEntityGraphs({ @NamedEntityGraph(name = "Action.ds", attributeNodes = { @NamedAttributeNode("distributionSet") }), @NamedEntityGraph(name = "Action.all", attributeNodes = { @NamedAttributeNode("distributionSet"), @NamedAttributeNode(value = "target", subgraph = "target.ds") }, - subgraphs = @NamedSubgraph(name = "target.ds", attributeNodes = @NamedAttributeNode("assignedDistributionSet"))) }) + subgraphs = @NamedSubgraph(name = "target.ds", attributeNodes = @NamedAttributeNode("assignedDistributionSet"))) +}) @Entity -// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for -// sub entities +// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for sub entities @SuppressWarnings("squid:S2160") public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Action, EventAwareEntity { + @Serial private static final long serialVersionUID = 1L; @ManyToOne(fetch = FetchType.LAZY, optional = false) @@ -86,13 +95,20 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio @Column(name = "active") private boolean active; + @Converter + public static class ActionTypeConverter extends MapTypeConverter { + + public ActionTypeConverter() { + super(Map.of( + ActionType.FORCED, 0, + ActionType.SOFT, 1, + ActionType.TIMEFORCED, 2, + ActionType.DOWNLOAD_ONLY, 3 + )); + } + } @Column(name = "action_type", nullable = false) - @ObjectTypeConverter(name = "actionType", objectType = Action.ActionType.class, dataType = Integer.class, conversionValues = { - @ConversionValue(objectValue = "FORCED", dataValue = "0"), - @ConversionValue(objectValue = "SOFT", dataValue = "1"), - @ConversionValue(objectValue = "TIMEFORCED", dataValue = "2"), - @ConversionValue(objectValue = "DOWNLOAD_ONLY", dataValue = "3") }) - @Convert("actionType") + @Convert(converter = ActionTypeConverter.class) @NotNull private ActionType actionType; @@ -104,21 +120,28 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio @Max(Action.WEIGHT_MAX) private Integer weight; + @Converter + public static class StatusConverter extends MapTypeConverter { + + public StatusConverter() { + super(new HashMap<>() {{ + put(Status.FINISHED, 0); + put(Status.ERROR, 1); + put(Status.WARNING, 2); + put(Status.RUNNING, 3); + put(Status.CANCELED, 4); + put(Status.CANCELING, 5); + put(Status.RETRIEVED, 6); + put(Status.DOWNLOAD, 7); + put(Status.SCHEDULED, 8); + put(Status.CANCEL_REJECTED, 9); + put(Status.DOWNLOADED, 10); + put(Status.WAIT_FOR_CONFIRMATION, 11); + }}); + } + } @Column(name = "status", nullable = false) - @ObjectTypeConverter(name = "status", objectType = Action.Status.class, dataType = Integer.class, conversionValues = { - @ConversionValue(objectValue = "FINISHED", dataValue = "0"), - @ConversionValue(objectValue = "ERROR", dataValue = "1"), - @ConversionValue(objectValue = "WARNING", dataValue = "2"), - @ConversionValue(objectValue = "RUNNING", dataValue = "3"), - @ConversionValue(objectValue = "CANCELED", dataValue = "4"), - @ConversionValue(objectValue = "CANCELING", dataValue = "5"), - @ConversionValue(objectValue = "RETRIEVED", dataValue = "6"), - @ConversionValue(objectValue = "DOWNLOAD", dataValue = "7"), - @ConversionValue(objectValue = "SCHEDULED", dataValue = "8"), - @ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"), - @ConversionValue(objectValue = "DOWNLOADED", dataValue = "10"), - @ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11") }) - @Convert("status") + @Convert(converter = StatusConverter.class) @NotNull private Status status; @@ -401,4 +424,28 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio return getMaintenanceWindowStartTime() .map(start -> start.plus(MaintenanceScheduleHelper.convertToISODuration(maintenanceWindowDuration))); } -} + + private static class MapTypeConverter implements AttributeConverter { + + private final Map javaToDbMap; + private final Map dbToJavaMap; + + protected MapTypeConverter(final Map javaToDbMap) { + this.javaToDbMap = javaToDbMap; + this.dbToJavaMap = javaToDbMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); + if (javaToDbMap.size() != dbToJavaMap.size()) { + throw new IllegalArgumentException("Duplicate values in javaToDbMap"); + } + } + + @Override + public DB_TYPE convertToDatabaseColumn(final JAVA_TYPE attribute) { + return javaToDbMap.get(attribute); + } + + @Override + public JAVA_TYPE convertToEntityAttribute(final DB_TYPE dbData) { + return dbToJavaMap.get(dbData); + } + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ActionTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ActionTest.java index dc7ba044c..b6f3c514e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ActionTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ActionTest.java @@ -17,13 +17,16 @@ import io.qameta.allure.Description; import io.qameta.allure.Feature; import io.qameta.allure.Story; import org.awaitility.Awaitility; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; +import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.Action.Status; import org.junit.jupiter.api.Test; @Feature("Unit Tests - Repository") @Story("Deployment Management") -public class ActionTest { +public class ActionTest extends AbstractJpaIntegrationTest { @Test @Description("Ensures that timeforced moded switch from soft to forces after defined timeframe.") @@ -41,4 +44,42 @@ public class ActionTest { Awaitility.await().atMost(Duration.ofSeconds(2)).pollInterval(Duration.ofMillis(100)) .until(timeforcedAction::isForcedOrTimeForced); } + + @Test + public void testActionTypeConvert() { + final long id = createAction().getId(); + for (final ActionType actionType : ActionType.values()) { + final JpaAction action = actionRepository + .findById(id).orElseThrow(() -> new IllegalStateException("Action not found")); + action.setActionType(actionType); + actionRepository.save(action); + assertThat(actionRepository.findById(id).orElseThrow(() -> new IllegalStateException("Action not found")) + .getActionType()).isEqualTo(actionType); + } + } + + @Test + public void testStatusConvert() { + final long id = createAction().getId(); + for (final Status status : Status.values()) { + final JpaAction action = actionRepository + .findById(id).orElseThrow(() -> new IllegalStateException("Action not found")); + action.setStatus(status); + actionRepository.save(action); + assertThat(actionRepository.findById(id).orElseThrow(() -> new IllegalStateException("Action not found")) + .getStatus()).isEqualTo(status); + } + } + + private Action createAction() { + final ActionType[] actionTypes = ActionType.values(); + final JpaAction action = new JpaAction(); + action.setTarget(testdataFactory.createTarget("testActionTypeMappingTarget")); + action.setDistributionSet(testdataFactory.createDistributionSet("testActionTypeMappingDS")); + action.setActionType(actionTypes[0]); + action.setStatus(Status.SCHEDULED); + action.setWeight(100); + action.setInitiatedBy("none"); + return actionRepository.save(action); + } }