Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## v8.0.0 YYYY-mm-DD
### Breaking changes
* Migrate to Spring Boot v4.x ([MODNOTES-282](https://folio-org.atlassian.net/browse/MODNOTES-282))
* Migrate to Spring Boot v4.x ([MODNOTES-282](https://folio-org.atlassian.net/browse/MODNOTES-282)), ([MODNOTES-293](https://folio-org.atlassian.net/browse/MODNOTES-293))

### New APIs versions
* Provides `API_NAME vX.Y`
Expand Down
16 changes: 13 additions & 3 deletions src/main/java/org/folio/notes/domain/entity/BaseEntity.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
package org.folio.notes.domain.entity;

import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PostLoad;
import jakarta.persistence.PostPersist;
import jakarta.persistence.Transient;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.domain.Persistable;

@Getter
@Setter
@MappedSuperclass
public abstract class BaseEntity {
public abstract class BaseEntity implements Persistable<UUID> {

@Id
@GeneratedValue
@Column(name = "id", updatable = false, nullable = false)
private UUID id;

private @Transient boolean isNew = false;

@PostLoad
@PostPersist
void markNotNew() {
this.isNew = false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
Expand Down Expand Up @@ -57,7 +58,7 @@ public class NoteEntity extends AuditableEntity {
@Column(name = "pop_up_on_check_out")
private boolean popUpOnCheckOut;

@ManyToOne(optional = false)
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "type_id", nullable = false)
private NoteTypeEntity type;

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/folio/notes/domain/mapper/LinkMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface LinkMapper {
Link toDtoLink(LinkEntity entity);

@Mapping(target = "id", ignore = true)
@Mapping(target = "notes", ignore = true)
@Mapping(target = "new", ignore = true)
@InheritInverseConfiguration
LinkEntity toEntityLink(Link dto);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public interface NoteTypesMapper {
@Mapping(target = "metadata", source = "entity", qualifiedByName = "BaseMetadataMapper")
NoteType toDto(NoteTypeEntity entity);

@Mapping(target = "new", ignore = true)
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "updatedDate", ignore = true)
@Mapping(target = "createdBy", ignore = true)
Expand All @@ -37,6 +38,7 @@ default NoteTypeCollection toDtoCollection(Page<NoteTypeEntity> entityList, Map<

List<NoteType> toDtoList(List<NoteTypeEntity> entityList);

@Mapping(target = "new", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "updatedDate", ignore = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public interface NotesMapper {
@Mapping(target = "type", expression = "java(entity.getType().getName())")
Note toDto(NoteEntity entity);

@Mapping(target = "new", ignore = true)
@Mapping(target = "type.id", source = "typeId")
@Mapping(target = "indexedContent", ignore = true)
@Mapping(target = "createdDate", ignore = true)
Expand All @@ -27,6 +28,7 @@ public interface NotesMapper {
@Mapping(target = "updatedBy", ignore = true)
NoteEntity toEntity(Note dto);

@Mapping(target = "new", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "type.id", source = "typeId")
@Mapping(target = "indexedContent", ignore = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.folio.notes.exception.NoteTypeNotFoundException;
import org.folio.notes.exception.NoteTypesLimitReached;
import org.folio.notes.service.NoteTypesService;
import org.folio.notes.util.JpaUtils;
import org.folio.spring.data.OffsetRequest;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -58,7 +59,7 @@ public NoteType getNoteType(UUID id) {
public NoteType createNoteType(NoteType noteType) {
log.debug("createNoteType:: trying to create note type with name: {}", noteType.getName());
validateNoteTypeLimit();
NoteTypeEntity entity = repository.save(mapper.toEntity(noteType));
NoteTypeEntity entity = repository.save(JpaUtils.initNewEntity(mapper.toEntity(noteType)));
log.info("createNoteType:: created note type with name: {}", entity.getName());
return mapper.toDto(entity);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.folio.notes.domain.repository.NoteRepository.linkIs;
import static org.folio.notes.domain.repository.NoteRepository.linkIsNot;
import static org.folio.notes.domain.repository.NoteRepository.typeNameIn;
import static org.folio.notes.util.JpaUtils.initNewEntity;

import java.util.List;
import java.util.Map;
Expand All @@ -26,11 +27,11 @@
import org.folio.notes.domain.entity.LinkEntity;
import org.folio.notes.domain.entity.NoteEntity;
import org.folio.notes.domain.entity.NoteEntity_;
import org.folio.notes.domain.entity.NoteTypeEntity;
import org.folio.notes.domain.mapper.NoteCollectionMapper;
import org.folio.notes.domain.mapper.NotesMapper;
import org.folio.notes.domain.repository.LinkRepository;
import org.folio.notes.domain.repository.NoteRepository;
import org.folio.notes.domain.repository.NoteTypesRepository;
import org.folio.notes.exception.NoteNotFoundException;
import org.folio.notes.service.NotesService;
import org.folio.notes.util.HtmlSanitizer;
Expand Down Expand Up @@ -71,6 +72,7 @@ public class NotesServiceImpl implements NotesService {

private final NoteRepository noteRepository;
private final LinkRepository linkRepository;
private final NoteTypesRepository noteTypesRepository;
private final NotesMapper notesMapper;
private final NoteCollectionMapper noteCollectionMapper;
private final HtmlSanitizer sanitizer;
Expand Down Expand Up @@ -126,7 +128,7 @@ public Note getNote(UUID id) {
public Note createNote(Note note) {
log.debug("createNote:: trying to create note by title: {}, domain: {}, type: {}",
note.getTitle(), note.getDomain(), note.getType());
NoteEntity entity = saveNote(note, notesMapper::toEntity);
NoteEntity entity = saveNote(note, dto -> initNewEntity(notesMapper.toEntity(dto)));
log.info("createNote:: created note by title: {}, domain: {}, type: {}",
note.getTitle(), note.getDomain(), note.getType());
return notesMapper.toDto(entity);
Expand Down Expand Up @@ -230,7 +232,9 @@ private Sort.Direction getOrderDirection(NotesOrderBy orderBy, OrderDirection or

private Function<Note, NoteEntity> noteMapFunction(Note dto, NoteEntity noteEntity) {
if (!dto.getTypeId().equals(noteEntity.getType().getId())) {
noteEntity.setType(new NoteTypeEntity());
var noteType = noteTypesRepository.findById(dto.getTypeId())
.orElseThrow(() -> new IllegalArgumentException("Note type with ID [" + dto.getTypeId() + "] was not found"));
noteEntity.setType(noteType);
}

return noteDto -> notesMapper.updateNote(noteDto, noteEntity);
Expand All @@ -254,20 +258,20 @@ private void manageNoteLinks(NoteEntity noteEntity) {
}
}

private LinkEntity fetchOrSaveLink(LinkEntity linkEntity) {
return fetchOrSaveLink(linkEntity.getObjectId(), linkEntity.getObjectType());
}

private LinkEntity fetchOrSaveLink(String objectId, String objectType) {
return linkRepository.findByObjectIdAndObjectType(objectId, objectType)
.orElseGet(() -> {
LinkEntity linkEntity = new LinkEntity();
linkEntity.setObjectId(objectId);
linkEntity.setObjectType(objectType);
return linkRepository.save(linkEntity);
return linkRepository.save(initNewEntity(linkEntity));
});
}

private LinkEntity fetchOrSaveLink(LinkEntity linkEntity) {
return fetchOrSaveLink(linkEntity.getObjectId(), linkEntity.getObjectType());
}

private NoteNotFoundException notFoundException(UUID id) {
return new NoteNotFoundException(id);
}
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/org/folio/notes/util/JpaUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.folio.notes.util;

import java.util.UUID;
import lombok.experimental.UtilityClass;
import org.folio.notes.domain.entity.BaseEntity;
import org.jspecify.annotations.NonNull;

@UtilityClass
public class JpaUtils {

public static <E extends BaseEntity> E initNewEntity(@NonNull E entity) {
if (entity.getId() == null) {
entity.setId(UUID.randomUUID());
}
entity.setNew(true);
return entity;
}
}
63 changes: 46 additions & 17 deletions src/test/java/org/folio/notes/controller/NotesControllerIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import static org.folio.notes.support.DatabaseHelper.LINK;
import static org.folio.notes.support.DatabaseHelper.NOTE;
import static org.folio.notes.support.DatabaseHelper.TYPE;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.equalTo;
Expand Down Expand Up @@ -47,6 +46,7 @@
import org.folio.notes.support.TestApiBase;
import org.folio.spring.cql.CqlQueryValidationException;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -228,6 +228,35 @@ void createNewNote() throws Exception {
assertEquals(1, rowsInTable);
}

@Test
@DisplayName("Create new note with links and id")
void createNewNoteWithLinksAndId() throws Exception {
String title = "First";
var note = new Note()
.id(UUID.randomUUID())
.title(title)
.domain("eholdings")
.content("<p>This is test content</p>")
.links(List.of(new Link("18-3207206", "package")));

var noteType = new NoteType().name(insecure().nextAlphabetic(100));
var contentAsString = mockMvc.perform(postNoteType(noteType)).andReturn().getResponse().getContentAsString();
var existingNoteType = OBJECT_MAPPER.readValue(contentAsString, NoteType.class);

note.setTypeId(existingNoteType.getId());

mockMvc.perform(postNote(note))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.title", is(title)))
.andExpect(jsonPath("$.metadata.createdByUserId").value(USER_ID.toString()))
.andExpect(jsonPath("$.metadata.createdDate").isNotEmpty())
.andExpect(header().string(HttpHeaders.LOCATION,
matchesRegex(
NOTE_URL + "/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$")));
var rowsInTable = databaseHelper.countRowsInTable(TENANT, NOTE);
assertEquals(1, rowsInTable);
}

// Tests for POST

@Test
Expand Down Expand Up @@ -509,7 +538,7 @@ void shouldRemoveNoteWhenLastLinkIsRemoved() throws Exception {
.type(PACKAGE_TYPE)
));

mockMvc.perform(postNote(note)).andExpect(status().isCreated());
mockMvc.perform(putById(note.getId(), note)).andExpect(status().isNoContent());
removeLinks(note.getId());
List<Note> notes = getNotes();

Expand Down Expand Up @@ -755,8 +784,8 @@ void shouldReturnListOfNotesSortedByCreatedDateAsc() throws Exception {
var firstNote = generateNote();
var secondNote = generateNote();

mockMvc.perform(postNote(firstNote)).andExpect(status().isCreated());
mockMvc.perform(postNote(secondNote)).andExpect(status().isCreated());
mockMvc.perform(putById(firstNote.getId(), firstNote)).andExpect(status().isNoContent());
mockMvc.perform(putById(secondNote.getId(), secondNote)).andExpect(status().isNoContent());

createLinks(firstNote.getId());
createLinks(secondNote.getId());
Expand All @@ -775,8 +804,8 @@ void shouldReturnListOfNotesSortedByCreatedDateDesc() throws Exception {
var firstNote = generateNote();
var secondNote = generateNote();

mockMvc.perform(postNote(firstNote)).andExpect(status().isCreated());
mockMvc.perform(postNote(secondNote)).andExpect(status().isCreated());
mockMvc.perform(putById(firstNote.getId(), firstNote)).andExpect(status().isNoContent());
mockMvc.perform(putById(secondNote.getId(), secondNote)).andExpect(status().isNoContent());

createLinks(firstNote.getId());
createLinks(secondNote.getId());
Expand Down Expand Up @@ -831,7 +860,7 @@ void shouldReturnListOfNotesSearchedByContent() throws Exception {
@DisplayName("Should interpret special regex characters literally")
void shouldInterpretSpecialRegexCharactersLiterally() throws Exception {
var firstNote = generateNote().title("a[abc1}{]z");
mockMvc.perform(postNote(firstNote)).andExpect(status().isCreated());
mockMvc.perform(putById(firstNote.getId(), firstNote)).andExpect(status().isNoContent());

var content = getNoteLinks("/note-links/domain/" + DOMAIN + "/type/" + PACKAGE_TYPE + "/id/" + PACKAGE_ID_1
+ "?search=a[abc1}{]z");
Expand All @@ -848,9 +877,9 @@ void shouldReturnListOfAssignedNotesSearchedAndSortedByTitle() throws Exception
var secondNote = generateNote().title("Title ZZZ ABC");
var thirdNote = generateNote().title("Title BBB");

mockMvc.perform(postNote(firstNote)).andExpect(status().isCreated());
mockMvc.perform(postNote(secondNote)).andExpect(status().isCreated());
mockMvc.perform(postNote(thirdNote)).andExpect(status().isCreated());
mockMvc.perform(putById(firstNote.getId(), firstNote)).andExpect(status().isNoContent());
mockMvc.perform(putById(secondNote.getId(), secondNote)).andExpect(status().isNoContent());
mockMvc.perform(putById(thirdNote.getId(), thirdNote)).andExpect(status().isNoContent());

createLinks(firstNote.getId());
createLinks(secondNote.getId());
Expand All @@ -870,9 +899,9 @@ void shouldReturnListOfAssignedNotesSearchedAndSortedByTitleOrderDesc() throws E
var secondNote = generateNote().title("Title ZZZ ABC");
var thirdNote = generateNote().title("Title BBB");

mockMvc.perform(postNote(firstNote)).andExpect(status().isCreated());
mockMvc.perform(postNote(secondNote)).andExpect(status().isCreated());
mockMvc.perform(postNote(thirdNote)).andExpect(status().isCreated());
mockMvc.perform(putById(firstNote.getId(), firstNote)).andExpect(status().isNoContent());
mockMvc.perform(putById(secondNote.getId(), secondNote)).andExpect(status().isNoContent());
mockMvc.perform(putById(thirdNote.getId(), thirdNote)).andExpect(status().isNoContent());

createLinks(firstNote.getId());
createLinks(secondNote.getId());
Expand Down Expand Up @@ -954,9 +983,9 @@ void shouldReturnNoteListWhenSearchByTitleAndNoteType() throws Exception {
+ "?search=" + noteTitle + "&noteType=" + NOTE_TYPE_NAME_1 + "&order=ASC");
var notes = OBJECT_MAPPER.readValue(content, NoteCollection.class).getNotes();

assertThat(notes.size(), equalTo(1));
assertThat(notes.getFirst().getTypeId(), equalTo(UUID.fromString(NOTE_TYPE_ID_2)));
assertThat(notes.getFirst().getTitle(), equalTo(noteTitle));
MatcherAssert.assertThat(notes.size(), equalTo(1));
MatcherAssert.assertThat(notes.getFirst().getTypeId(), equalTo(UUID.fromString(NOTE_TYPE_ID_2)));
MatcherAssert.assertThat(notes.getFirst().getTitle(), equalTo(noteTitle));
}

@Test
Expand Down Expand Up @@ -1178,7 +1207,7 @@ private ResultMatcher errorMessageMatch(Matcher<String> errorMessageMatcher) {
}

private <T> ResultMatcher exceptionMatch(Class<T> type) {
return result -> assertThat(result.getResolvedException(), instanceOf(type));
return result -> MatcherAssert.assertThat(result.getResolvedException(), instanceOf(type));
}
}

Loading