Improve audit properties handling to be hibernate comapttible (#2140)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -90,7 +90,7 @@ public final class JpaManagementHelper {
|
|||||||
// log written because modifying e.g. metadata is modifying the base
|
// log written because modifying e.g. metadata is modifying the base
|
||||||
// entity itself for auditing purposes.
|
// entity itself for auditing purposes.
|
||||||
final J result = entityManager.merge(entity);
|
final J result = entityManager.merge(entity);
|
||||||
result.setLastModifiedAt(0L);
|
result.setLastModifiedAt(1);
|
||||||
|
|
||||||
return repository.save(result);
|
return repository.save(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|||||||
* Base hawkBit entity class containing the common attributes.
|
* Base hawkBit entity class containing the common attributes.
|
||||||
*/
|
*/
|
||||||
@NoArgsConstructor(access = AccessLevel.PROTECTED) // Default constructor needed for JPA entities.
|
@NoArgsConstructor(access = AccessLevel.PROTECTED) // Default constructor needed for JPA entities.
|
||||||
@Setter
|
|
||||||
@Getter
|
|
||||||
@MappedSuperclass
|
@MappedSuperclass
|
||||||
@EntityListeners({ AuditingEntityListener.class, EntityInterceptorListener.class })
|
@EntityListeners({ AuditingEntityListener.class, EntityInterceptorListener.class })
|
||||||
public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
||||||
@@ -53,17 +51,21 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Setter // should be used just for test purposes
|
||||||
|
@Getter
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@Column(name = "id")
|
@Column(name = "id")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
@Setter // should be used just for test purposes
|
||||||
|
@Getter
|
||||||
@Version
|
@Version
|
||||||
@Column(name = "optlock_revision")
|
@Column(name = "optlock_revision")
|
||||||
private int optLockRevision;
|
private int optLockRevision;
|
||||||
|
|
||||||
// Audit fields. use property access to ensure that setters will be called and checked for modification
|
// Audit fields. use property access to ensure that setters will be called and checked for modification
|
||||||
// (touch implementation depends on setLastModifiedAt(0).
|
// (touch implementation depends on setLastModifiedAt(1).
|
||||||
@Column(name = "created_by", updatable = false, nullable = false, length = USERNAME_FIELD_LENGTH)
|
@Column(name = "created_by", updatable = false, nullable = false, length = USERNAME_FIELD_LENGTH)
|
||||||
private String createdBy;
|
private String createdBy;
|
||||||
@Column(name = "created_at", updatable = false, nullable = false)
|
@Column(name = "created_at", updatable = false, nullable = false)
|
||||||
@@ -75,15 +77,6 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
|
|
||||||
@CreatedBy
|
@CreatedBy
|
||||||
public void setCreatedBy(final String createdBy) {
|
public void setCreatedBy(final String createdBy) {
|
||||||
if (isController()) {
|
|
||||||
this.createdBy = "CONTROLLER_PLUG_AND_PLAY";
|
|
||||||
|
|
||||||
// In general modification audit entry is not changed by the controller.
|
|
||||||
// However, we want to stay consistent with EnableJpaAuditing#modifyOnCreate=true.
|
|
||||||
this.lastModifiedBy = this.createdBy;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.createdBy = createdBy;
|
this.createdBy = createdBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,15 +89,9 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
@CreatedDate
|
@CreatedDate
|
||||||
public void setCreatedAt(final long createdAt) {
|
public void setCreatedAt(final long createdAt) {
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
|
|
||||||
// In general modification audit entry is not changed by the controller.
|
|
||||||
// However, we want to stay consistent with EnableJpaAuditing#modifyOnCreate=true.
|
|
||||||
if (isController()) {
|
|
||||||
this.lastModifiedAt = createdAt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Column(name = "created_at", updatable = false, nullable = false)
|
// property access to make entity manager to detect touch
|
||||||
@Access(AccessType.PROPERTY)
|
@Access(AccessType.PROPERTY)
|
||||||
public long getCreatedAt() {
|
public long getCreatedAt() {
|
||||||
return createdAt;
|
return createdAt;
|
||||||
@@ -112,22 +99,24 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
|
|
||||||
@LastModifiedBy
|
@LastModifiedBy
|
||||||
public void setLastModifiedBy(final String lastModifiedBy) {
|
public void setLastModifiedBy(final String lastModifiedBy) {
|
||||||
if (isController()) {
|
if (lastModifiedBy != null && isController()) {
|
||||||
|
// initialized and controller = doesn't update
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastModifiedBy = lastModifiedBy;
|
this.lastModifiedBy = lastModifiedBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Column(name = "last_modified_by", nullable = false, length = USERNAME_FIELD_LENGTH)
|
// property access to make entity manager to detect touch
|
||||||
@Access(AccessType.PROPERTY)
|
@Access(AccessType.PROPERTY)
|
||||||
public String getLastModifiedBy() {
|
public String getLastModifiedBy() {
|
||||||
return lastModifiedBy;
|
return lastModifiedBy == null ? createdBy : lastModifiedBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@LastModifiedDate
|
@LastModifiedDate
|
||||||
public void setLastModifiedAt(final long lastModifiedAt) {
|
public void setLastModifiedAt(final long lastModifiedAt) {
|
||||||
if (isController()) {
|
if (lastModifiedAt != 0 && isController()) {
|
||||||
|
// initialized and controller = doesn't update
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +126,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
|
|||||||
// property access to make entity manager to detect touch
|
// property access to make entity manager to detect touch
|
||||||
@Access(AccessType.PROPERTY)
|
@Access(AccessType.PROPERTY)
|
||||||
public long getLastModifiedAt() {
|
public long getLastModifiedAt() {
|
||||||
return lastModifiedAt;
|
return lastModifiedAt == 0 ? createdAt : lastModifiedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
package org.eclipse.hawkbit.repository.jpa.model;
|
package org.eclipse.hawkbit.repository.jpa.model;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
@ExpectEvents({
|
@ExpectEvents({
|
||||||
@Expect(type = TargetCreatedEvent.class, count = 1),
|
@Expect(type = TargetCreatedEvent.class, count = 1),
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 2) })
|
@Expect(type = TargetUpdatedEvent.class, count = 2) })
|
||||||
public void updateTargetAttributesFailsIfTooManyEntries() throws Exception {
|
void updateTargetAttributesFailsIfTooManyEntries() throws Exception {
|
||||||
final String controllerId = "test123";
|
final String controllerId = "test123";
|
||||||
final int allowedAttributes = quotaManagement.getMaxAttributeEntriesPerTarget();
|
final int allowedAttributes = quotaManagement.getMaxAttributeEntriesPerTarget();
|
||||||
testdataFactory.createTarget(controllerId);
|
testdataFactory.createTarget(controllerId);
|
||||||
@@ -139,7 +139,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Checks if invalid values of attribute-key and attribute-value are handled correctly")
|
@Description("Checks if invalid values of attribute-key and attribute-value are handled correctly")
|
||||||
public void updateTargetAttributesFailsForInvalidAttributes() {
|
void updateTargetAttributesFailsForInvalidAttributes() {
|
||||||
final String controllerId = "targetId123";
|
final String controllerId = "targetId123";
|
||||||
testdataFactory.createTarget(controllerId);
|
testdataFactory.createTarget(controllerId);
|
||||||
|
|
||||||
@@ -175,22 +175,23 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1) })
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1) })
|
||||||
public void controllerProvidesIntermediateFeedbackFailsIfQuotaHit() {
|
void controllerProvidesIntermediateFeedbackFailsIfQuotaHit() throws Exception {
|
||||||
final int allowStatusEntries = 10;
|
final int allowStatusEntries = 10;
|
||||||
final Long actionId = createTargetAndAssignDs();
|
final Long actionId = createTargetAndAssignDs();
|
||||||
|
|
||||||
// Fails as one entry is already in there from the assignment
|
SecurityContextSwitch
|
||||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
.runAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS), () -> {
|
||||||
.isThrownBy(() -> SecurityContextSwitch
|
// Fails as one entry is already in there from the assignment
|
||||||
.runAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS), () -> {
|
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
||||||
writeStatus(actionId, allowStatusEntries);
|
.isThrownBy(() -> writeStatus(actionId, allowStatusEntries))
|
||||||
return null;
|
.withMessageContaining(String.valueOf(allowStatusEntries));
|
||||||
})).withMessageContaining("" + allowStatusEntries);
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Test to verify the storage and retrieval of action history.")
|
@Description("Test to verify the storage and retrieval of action history.")
|
||||||
public void findMessagesByActionStatusId() {
|
void findMessagesByActionStatusId() {
|
||||||
final DistributionSet testDs = testdataFactory.createDistributionSet("1");
|
final DistributionSet testDs = testdataFactory.createDistributionSet("1");
|
||||||
final List<Target> testTarget = testdataFactory.createTargets(1);
|
final List<Target> testTarget = testdataFactory.createTargets(1);
|
||||||
|
|
||||||
@@ -225,7 +226,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
@Expect(type = ActionCreatedEvent.class, count = 2),
|
@Expect(type = ActionCreatedEvent.class, count = 2),
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 2),
|
@Expect(type = TargetUpdatedEvent.class, count = 2),
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2) })
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2) })
|
||||||
public void addActionStatusUpdatesUntilQuotaIsExceeded() {
|
void addActionStatusUpdatesUntilQuotaIsExceeded() {
|
||||||
|
|
||||||
// any distribution set assignment causes 1 status entity to be created
|
// any distribution set assignment causes 1 status entity to be created
|
||||||
final int maxStatusEntries = quotaManagement.getMaxStatusEntriesPerAction() - 1;
|
final int maxStatusEntries = quotaManagement.getMaxStatusEntriesPerAction() - 1;
|
||||||
@@ -265,7 +266,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1) })
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1) })
|
||||||
public void createActionStatusWithTooManyMessages() {
|
void createActionStatusWithTooManyMessages() {
|
||||||
|
|
||||||
final int maxMessages = quotaManagement.getMaxMessagesPerActionStatus();
|
final int maxMessages = quotaManagement.getMaxMessagesPerActionStatus();
|
||||||
|
|
||||||
@@ -297,7 +298,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||||
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
@Expect(type = TargetUpdatedEvent.class, count = 1),
|
||||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1) })
|
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1) })
|
||||||
public void controllerReportsDownloadForDownloadOnlyAction() {
|
void controllerReportsDownloadForDownloadOnlyAction() {
|
||||||
testdataFactory.createTarget();
|
testdataFactory.createTarget();
|
||||||
final Long actionId = createAndAssignDsAsDownloadOnly("downloadOnlyDs", DEFAULT_CONTROLLER_ID);
|
final Long actionId = createAndAssignDsAsDownloadOnly("downloadOnlyDs", DEFAULT_CONTROLLER_ID);
|
||||||
assertThat(actionId).isNotNull();
|
assertThat(actionId).isNotNull();
|
||||||
@@ -932,8 +933,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
testdataFactory.addSoftwareModuleMetadata(set);
|
testdataFactory.addSoftwareModuleMetadata(set);
|
||||||
|
|
||||||
final Map<Long, List<SoftwareModuleMetadata>> result = controllerManagement
|
final Map<Long, List<SoftwareModuleMetadata>> result = controllerManagement
|
||||||
.findTargetVisibleMetaDataBySoftwareModuleId(
|
.findTargetVisibleMetaDataBySoftwareModuleId(set.getModules().stream().map(SoftwareModule::getId).toList());
|
||||||
set.getModules().stream().map(SoftwareModule::getId).collect(Collectors.toList()));
|
|
||||||
|
|
||||||
assertThat(result).hasSize(3);
|
assertThat(result).hasSize(3);
|
||||||
result.forEach((key, value) -> assertThat(value).hasSize(1));
|
result.forEach((key, value) -> assertThat(value).hasSize(1));
|
||||||
|
|||||||
Reference in New Issue
Block a user