Skip to content

Commit

Permalink
parent a75ab33
Browse files Browse the repository at this point in the history
author Andrei Bordak <[email protected]> 1733336210 +0400
committer Andrei Bordak <[email protected]> 1733990640 +0400

MODLD-594: Resource date fields

MODLD-594: Remove unnecessary test

MODLD-594: Extracted migration

MODLD-594 MErge commit

MODLD-594 MErge commit

MODLD-594: Remove temp dependency

MODLD-594: Redundant import
  • Loading branch information
AndreiBordak committed Dec 12, 2024
1 parent a75ab33 commit 112ca64
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.folio.linked.data.configuration.jpa.audit;

import static java.util.Optional.ofNullable;

import jakarta.persistence.PrePersist;
import lombok.AllArgsConstructor;
import org.folio.linked.data.model.entity.Resource;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.data.auditing.AuditingHandler;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.stereotype.Component;

@Component
@AllArgsConstructor
public class LinkedDataAuditEntityListener extends AuditingEntityListener {

private ObjectFactory<AuditingHandler> handler;

@Override
@PrePersist
public void touchForCreate(Object target) {
if (target instanceof Resource resource) {
if (resource.getCreatedBy() == null) {
ofNullable(handler.getObject())
.ifPresent(object -> object.markCreated(target));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.folio.linked.data.configuration.jpa.audit;

import java.util.Optional;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.folio.spring.FolioExecutionContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing(modifyOnCreate = false)
@RequiredArgsConstructor
public class LinkedDataAuditorAware implements AuditorAware<UUID> {

private final FolioExecutionContext folioExecutionContext;

@Override
public Optional<UUID> getCurrentAuditor() {
return Optional.ofNullable(folioExecutionContext.getUserId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.folio.linked.data.domain.dto.EventMetadata;
import org.folio.linked.data.domain.dto.ParsedRecord;
import org.folio.linked.data.domain.dto.SourceRecord;
import org.folio.linked.data.domain.dto.SourceRecordDomainEvent;
Expand All @@ -25,6 +26,7 @@ public class SourceRecordDomainEventDeserializer extends JsonDeserializer<Source
private static final String ID = "id";
private static final String EVENT_TYPE = "eventType";
private static final String EVENT_PAYLOAD = "eventPayload";
private static final String EVENT_METADATA = "eventMetadata";
private static final String DELETED = "deleted";
private static final String PARSED_RECORD = "parsedRecord";
private static final String CONTENT = "content";
Expand All @@ -44,9 +46,24 @@ public SourceRecordDomainEvent deserialize(JsonParser jp, DeserializationContext
if (node.has(EVENT_PAYLOAD)) {
event.setEventPayload(getSourceRecord(node.get(EVENT_PAYLOAD)));
}
if (node.has(EVENT_METADATA)) {
event.setEventMetadata(getEventMetadata(node));
}
return event;
}

private EventMetadata getEventMetadata(JsonNode node) {
if (node.has(EVENT_METADATA)) {
var value = node.get(EVENT_METADATA);
try {
return value.isNull() ? null : objectMapper.readValue(value.toString(), EventMetadata.class);
} catch (Exception e) {
log.warn("Can't convert event metadata", e);
}
}
return null;
}

private EventTypeEnum getEventType(JsonNode node) {
var incoming = node.get(EVENT_TYPE).textValue();
try {
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/org/folio/linked/data/model/entity/Resource.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
Expand All @@ -26,10 +27,13 @@
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.Data;
import lombok.EqualsAndHashCode;
Expand All @@ -38,9 +42,13 @@
import lombok.ToString;
import lombok.experimental.Accessors;
import org.folio.ld.dictionary.ResourceTypeDictionary;
import org.folio.linked.data.configuration.jpa.audit.LinkedDataAuditEntityListener;
import org.folio.linked.data.validation.PrimaryTitleConstraint;
import org.folio.marc4ld.util.ResourceKind;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.domain.Persistable;

@Entity
Expand All @@ -51,6 +59,7 @@
@Table(name = "resources")
@EqualsAndHashCode(of = "id")
@SuppressWarnings("javaarchitecture:S7027")
@EntityListeners(LinkedDataAuditEntityListener.class)
public class Resource implements Persistable<Long> {

@Id
Expand Down Expand Up @@ -93,6 +102,25 @@ public class Resource implements Persistable<Long> {
@PrimaryKeyJoinColumn
private FolioMetadata folioMetadata;

@Column(name = "created_date", updatable = false, nullable = false)
private Timestamp createdDate;

@UpdateTimestamp
@Column(name = "updated_date", nullable = false)
private Timestamp updatedDate;

@CreatedBy
@Column(name = "created_by")
private UUID createdBy;

@LastModifiedBy
@Column(name = "updated_by")
private UUID updatedBy;

@Version
@Column(name = "version", nullable = false)
private long version;

@Transient
private boolean managed;

Expand All @@ -103,6 +131,11 @@ public Resource(@NonNull Resource that) {
this.folioMetadata = that.folioMetadata;
this.indexDate = that.indexDate;
this.types = new LinkedHashSet<>(that.getTypes());
this.createdDate = that.createdDate;
this.createdBy = that.createdBy;
this.updatedDate = that.updatedDate;
this.updatedBy = that.updatedBy;
this.version = that.version;
this.outgoingEdges = ofNullable(that.getOutgoingEdges())
.map(outEdges -> outEdges.stream().map(ResourceEdge::new).collect(Collectors.toSet()))
.orElse(null);
Expand Down Expand Up @@ -193,6 +226,9 @@ void prePersist() {
throw new IllegalStateException("Cannot save resource [" + id + "] with types " + types + ". "
+ "Folio metadata can be set only for instance and authority resources");
}
if (isNull(this.createdDate)) {
this.createdDate = new Timestamp(System.currentTimeMillis());
}
}

@PostRemove
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.folio.linked.data.service.resource.edge.ResourceEdgeService;
import org.folio.linked.data.service.resource.graph.ResourceGraphService;
import org.folio.linked.data.service.resource.meta.MetadataService;
import org.folio.spring.FolioExecutionContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
Expand All @@ -38,6 +39,7 @@ public class ResourceServiceImpl implements ResourceService {
private final RequestProcessingExceptionBuilder exceptionBuilder;
private final ApplicationEventPublisher applicationEventPublisher;
private final ResourceEdgeService resourceEdgeService;
private final FolioExecutionContext folioExecutionContext;

@Override
public ResourceResponseDto createResource(ResourceRequestDto resourceDto) {
Expand Down Expand Up @@ -103,6 +105,10 @@ private Resource saveNewResource(ResourceRequestDto resourceDto, Resource old) {
var mapped = resourceDtoMapper.toEntity(resourceDto);
metadataService.ensure(mapped, old.getFolioMetadata());
resourceEdgeService.copyOutgoingEdges(old, mapped);
mapped.setCreatedDate(old.getCreatedDate());
mapped.setVersion(old.getVersion() + 1);
mapped.setCreatedBy(old.getCreatedBy());
mapped.setUpdatedBy(folioExecutionContext.getUserId());
return resourceGraphService.saveMergingGraph(mapped);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@

<include file="functions/drop_staging_tables.sql" relativeToChangelogFile="true"/>
<include file="functions/create_staging_tables.sql" relativeToChangelogFile="true"/>
<include file="migration/resources_date_fields.sql" relativeToChangelogFile="true"/>
</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
alter table resources
add column created_date timestamp default current_timestamp not null,
add column updated_date timestamp default current_timestamp not null,
add column created_by uuid,
add column updated_by uuid,
add column version int default 0 not null;

comment on column resources.created_date is 'Date and time when resource first added to data graph';
comment on column resources.created_by is 'UUID of user who added resource to data graph';
comment on column resources.updated_date is 'Date and time when resource last updated';
comment on column resources.updated_by is 'UUID of user who performed the last update to the resource';
comment on column resources.version is 'Version of the resource';
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,20 @@
import static org.folio.linked.data.test.TestUtil.OBJECT_MAPPER;
import static org.folio.linked.data.test.TestUtil.SIMPLE_WORK_WITH_INSTANCE_REF_SAMPLE;
import static org.folio.linked.data.test.TestUtil.WORK_WITH_INSTANCE_REF_SAMPLE;
import static org.folio.linked.data.test.TestUtil.assertResourceMetadata;
import static org.folio.linked.data.test.TestUtil.cleanResourceTables;
import static org.folio.linked.data.test.TestUtil.defaultHeaders;
import static org.folio.linked.data.test.TestUtil.defaultHeadersWithUserId;
import static org.folio.linked.data.test.TestUtil.getSampleInstanceDtoMap;
import static org.folio.linked.data.test.TestUtil.getSampleWorkDtoMap;
import static org.folio.linked.data.test.TestUtil.randomLong;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -217,6 +221,7 @@ public abstract class ResourceControllerITBase {
private static final String GRANTING_INSTITUTION_REF = "_grantingInstitutionReference";
private static final String WORK_ID_PLACEHOLDER = "%WORK_ID%";
private static final String INSTANCE_ID_PLACEHOLDER = "%INSTANCE_ID%";
private static final UUID USER_ID = UUID.randomUUID();
@Autowired
private MockMvc mockMvc;
@Autowired
Expand Down Expand Up @@ -502,7 +507,7 @@ void createWorkWithInstanceRef_shouldSaveEntityCorrectly() throws Exception {
resourceTestService.saveGraph(instanceForReference);
var requestBuilder = post(RESOURCE_URL)
.contentType(APPLICATION_JSON)
.headers(defaultHeaders(env))
.headers(defaultHeadersWithUserId(env, USER_ID.toString()))
.content(
WORK_WITH_INSTANCE_REF_SAMPLE.replaceAll(INSTANCE_ID_PLACEHOLDER, instanceForReference.getId().toString())
);
Expand All @@ -523,6 +528,63 @@ void createWorkWithInstanceRef_shouldSaveEntityCorrectly() throws Exception {
validateWork(workResource, true);
checkSearchIndexMessage(workResource.getId(), CREATE);
checkIndexDate(workResource.getId().toString());
assertResourceMetadata(workResource, USER_ID, null);
}

@Test
void update_shouldReturnCorrectlyUpdateMetadataFields() throws Exception {
// given
var instanceForReference = getSampleInstanceResource(null, null);
setExistingResourcesIds(instanceForReference);
resourceTestService.saveGraph(instanceForReference);
var requestBuilder = post(RESOURCE_URL)
.contentType(APPLICATION_JSON)
.headers(defaultHeadersWithUserId(env, USER_ID.toString()))
.content(
WORK_WITH_INSTANCE_REF_SAMPLE.replaceAll(INSTANCE_ID_PLACEHOLDER, instanceForReference.getId().toString())
);

var response = mockMvc.perform(requestBuilder)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
var resourceResponse = OBJECT_MAPPER.readValue(response, ResourceResponseDto.class);
var originalWorkId = ((WorkResponseField) resourceResponse.getResource()).getWork().getId();
var originalWorkResource = resourceTestService.getResourceById(originalWorkId, 4);


var updateDto = getSampleWorkDtoMap();
var workMap = (LinkedHashMap) ((LinkedHashMap) updateDto.get("resource")).get(WORK.getUri());
workMap.put(PropertyDictionary.LANGUAGE.getValue(),
Map.of(
LINK.getValue(), List.of("http://id.loc.gov/vocabulary/languages/rus"),
TERM.getValue(), List.of("Russian")
));
var updatedById = UUID.randomUUID();

// when
var updateRequest = put(RESOURCE_URL + "/" + originalWorkId)
.contentType(APPLICATION_JSON)
.headers(defaultHeadersWithUserId(env, updatedById.toString()))
.content(OBJECT_MAPPER.writeValueAsString(updateDto)
.replaceAll(INSTANCE_ID_PLACEHOLDER, instanceForReference.getId().toString())
);

// then
var updatedResponse = mockMvc.perform(updateRequest).andReturn().getResponse().getContentAsString();
var updatedResourceResponse = OBJECT_MAPPER.readValue(updatedResponse, ResourceResponseDto.class);
var updatedWorkId = ((WorkResponseField) updatedResourceResponse.getResource()).getWork().getId();
var updatedWorkResource = resourceTestService.getResourceById(updatedWorkId, 4);
compareResourceMetadataOfOriginalAndUpdated(originalWorkResource, updatedWorkResource, updatedById);
}

private void compareResourceMetadataOfOriginalAndUpdated(Resource original, Resource updated, UUID updatedById) {
assertEquals(USER_ID, updated.getCreatedBy());
assertEquals(updatedById, updated.getUpdatedBy());
assertTrue(updated.getUpdatedDate().after(original.getUpdatedDate()));
assertEquals(original.getCreatedDate(), updated.getCreatedDate());
assertEquals(original.getCreatedBy(), updated.getCreatedBy());
assertNull(original.getUpdatedBy());
assertEquals(1, updated.getVersion() - original.getVersion());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import org.folio.linked.data.domain.dto.EventMetadata;
import org.folio.linked.data.domain.dto.ParsedRecord;
import org.folio.linked.data.domain.dto.SourceRecord;
import org.folio.linked.data.domain.dto.SourceRecordDomainEvent;
Expand Down Expand Up @@ -50,7 +51,7 @@ void shouldHandleSrsDomainEvent_whenSourceRecordType_isMarcBib() {
.content(TestUtil.loadResourceAsString("samples/srsDomainEventParsedContent.txt"))
)
.deleted(true)
);
).eventMetadata(new EventMetadata().eventTTL(1).publishedBy("mod-source-record-storage-5.9.0-SNAPSHOT"));

// when
eventKafkaTemplate.send(eventProducerRecord);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.folio.linked.data.service.resource.edge.ResourceEdgeService;
import org.folio.linked.data.service.resource.graph.ResourceGraphService;
import org.folio.linked.data.service.resource.meta.MetadataService;
import org.folio.spring.FolioExecutionContext;
import org.folio.spring.testing.type.UnitTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -74,6 +75,8 @@ class ResourceServiceImplTest {
private RequestProcessingExceptionBuilder exceptionBuilder;
@Mock
private ResourceEdgeService resourceEdgeService;
@Mock
private FolioExecutionContext folioExecutionContext;

@Test
void create_shouldPersistMappedResourceAndNotPublishResourceCreatedEvent_forResourceWithNoWork() {
Expand Down Expand Up @@ -218,6 +221,7 @@ void update_shouldSaveUpdatedResourceAndSendResourceUpdatedEvent_forResourceWith
);
when(resourceDtoMapper.toDto(work)).thenReturn(expectedDto);
when(resourceGraphService.saveMergingGraph(work)).thenReturn(work);
when(folioExecutionContext.getUserId()).thenReturn(UUID.randomUUID());

// when
var result = resourceService.updateResource(id, workDto);
Expand All @@ -226,6 +230,7 @@ void update_shouldSaveUpdatedResourceAndSendResourceUpdatedEvent_forResourceWith
assertThat(expectedDto).isEqualTo(result);
verify(resourceGraphService).breakEdgesAndDelete(oldWork);
verify(resourceGraphService).saveMergingGraph(work);
verify(folioExecutionContext).getUserId();
verify(applicationEventPublisher).publishEvent(new ResourceUpdatedEvent(work));
}

Expand Down
Loading

0 comments on commit 112ca64

Please sign in to comment.