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
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public User create(UserPostRequest userPostRequest) {
* @param ttlMinutes time-to-live for the OTP (in minutes) shown in the email
*/
public void sendOtp(String to, String toName, String otp, int ttlMinutes) {
// mailService.sendOtpMail(to, toName, otp, ttlMinutes);
mailService.sendOtpMail(to, toName, otp, ttlMinutes);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ default Set<VsumRepresentation.View> toView(Set<VsumView> views) {
.map(
vsumViewMetaModel ->
vsumViewMetaModel.getMetaModel().getSource().getId())
.sorted()
.toList())
.fileStorageId(view.getFileStorage().getId())
.build())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package tools.vitruv.methodologist.vsum.model.repository;

import java.util.Collection;
import java.util.List;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import tools.vitruv.methodologist.vsum.model.VsumView;
import tools.vitruv.methodologist.vsum.model.VsumViewMetaModel;
Expand All @@ -21,10 +25,29 @@ public interface VsumViewMetaModelRepository extends CrudRepository<VsumViewMeta
*/
List<VsumViewMetaModel> findAllByVsumView(VsumView vsumView);

/**
* Retrieves all view-meta-model associations for the given collection of VSUM views in a single
* query, avoiding N+1 patterns when processing multiple views.
*
* @param views the VSUM views whose associated view-meta-model entries are requested
* @return a list of {@link VsumViewMetaModel} entities linked to any of the provided VSUM views
*/
List<VsumViewMetaModel> findAllByVsumViewIn(Collection<VsumView> views);

/**
* Deletes all view-meta-model associations for the given VSUM view.
*
* @param vsumView the VSUM view whose associated view-meta-model entries should be removed
*/
void deleteAllByVsumView(VsumView vsumView);

/**
* Deletes all view-meta-model associations for the given collection of VSUM views in a single
* bulk query.
*
* @param views the VSUM views whose associated view-meta-model entries should be removed
*/
@Modifying
@Query("DELETE FROM VsumViewMetaModel vm WHERE vm.vsumView IN :views")
void deleteAllByVsumViewIn(@Param("views") Collection<VsumView> views);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import tools.vitruv.methodologist.user.model.repository.UserRepository;
import tools.vitruv.methodologist.vsum.VsumRepresentation;
import tools.vitruv.methodologist.vsum.controller.dto.request.MetaModelRelationRequest;
import tools.vitruv.methodologist.vsum.controller.dto.request.ViewRequest;
import tools.vitruv.methodologist.vsum.controller.dto.request.VsumSyncChangesPutRequest;
import tools.vitruv.methodologist.vsum.controller.dto.response.VsumHistoryResponse;
import tools.vitruv.methodologist.vsum.mapper.VsumHistoryMapper;
Expand Down Expand Up @@ -202,6 +203,25 @@ private VsumSyncChangesPutRequest toSyncRequest(VsumRepresentation representatio
vsumSyncChangesPutRequest.setMetaModelRelationRequests(metaModelRelationRequests);
}

if (representation.getViews() == null) {
vsumSyncChangesPutRequest.setViewRequests(List.of());
} else {
List<ViewRequest> viewRequests =
representation.getViews().stream()
.filter(Objects::nonNull)
.map(
view ->
ViewRequest.builder()
.fileStorageId(view.getFileStorageId())
.metaModelIds(
view.getMetaModelIds() == null
? List.of()
: List.copyOf(view.getMetaModelIds()))
.build())
.toList();
vsumSyncChangesPutRequest.setViewRequests(viewRequests);
}

return vsumSyncChangesPutRequest;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -832,9 +832,7 @@ private String toViewKey(ViewRequest request) {
return request.getFileStorageId() + "|" + metaModelPart;
}

private String toViewKey(VsumView vsumView) {
List<VsumViewMetaModel> relations = vsumViewMetaModelRepository.findAllByVsumView(vsumView);

private String toViewKey(VsumView vsumView, List<VsumViewMetaModel> relations) {
String metaModelPart =
relations.stream()
.map(VsumViewMetaModel::getMetaModel)
Expand All @@ -855,9 +853,22 @@ private String toViewKey(VsumView vsumView) {
private ViewSyncPlan buildViewSyncPlan(Vsum vsum, List<ViewRequest> desiredViewRequests) {
List<VsumView> existingViews = vsumViewRepository.findAllByVsum(vsum);

Map<Long, List<VsumViewMetaModel>> relationsByViewId = new HashMap<>();
if (!existingViews.isEmpty()) {
List<VsumViewMetaModel> allRelations =
vsumViewMetaModelRepository.findAllByVsumViewIn(existingViews);
for (VsumViewMetaModel relation : allRelations) {
relationsByViewId
.computeIfAbsent(relation.getVsumView().getId(), k -> new ArrayList<>())
.add(relation);
}
}

Map<String, VsumView> existingByKey = new HashMap<>();
for (VsumView existingView : existingViews) {
existingByKey.put(toViewKey(existingView), existingView);
List<VsumViewMetaModel> viewRelations =
relationsByViewId.getOrDefault(existingView.getId(), List.of());
existingByKey.put(toViewKey(existingView, viewRelations), existingView);
}

Set<String> existingKeys = new HashSet<>(existingByKey.keySet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.StreamSupport;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.FieldDefaults;
Expand Down Expand Up @@ -34,14 +35,19 @@ public class VsumViewMetaModelService {
/**
* Creates associations between a view and the provided metamodel ids.
*
* <p>Only source metamodels are considered valid for association.
* <p>Only source metamodels are considered valid for association. A {@code null} or empty {@code
* metaModelIds} set results in an empty list being returned without querying the database.
*
* @param vsumView target view
* @param metaModelIds metamodel ids to associate
* @param metaModelIds metamodel ids to associate; may be {@code null} (treated as empty)
* @return persisted join entities
*/
@Transactional
public List<VsumViewMetaModel> create(VsumView vsumView, Set<Long> metaModelIds) {
if (metaModelIds == null || metaModelIds.isEmpty()) {
return List.of();
}

List<VsumMetaModel> vsumMetaModels =
vsumMetaModelRepository.findAllByVsumAndMetaModel_source_idIn(
vsumView.getVsum(), metaModelIds);
Expand All @@ -55,7 +61,9 @@ public List<VsumViewMetaModel> create(VsumView vsumView, Set<Long> metaModelIds)
.build());
}

return (List<VsumViewMetaModel>) vsumViewMetaModelRepository.saveAll(entities);
return StreamSupport.stream(
vsumViewMetaModelRepository.saveAll(entities).spliterator(), false)
.toList();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static tools.vitruv.methodologist.messages.Error.VIEW_FILE_ID_NOT_FOUND_ERROR;

import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.FieldDefaults;
Expand All @@ -14,6 +15,7 @@
import tools.vitruv.methodologist.general.model.repository.FileStorageRepository;
import tools.vitruv.methodologist.vsum.model.Vsum;
import tools.vitruv.methodologist.vsum.model.VsumView;
import tools.vitruv.methodologist.vsum.model.repository.VsumViewMetaModelRepository;
import tools.vitruv.methodologist.vsum.model.repository.VsumViewRepository;

/**
Expand All @@ -30,6 +32,7 @@ public class VsumViewService {

VsumViewRepository vsumViewRepository;
FileStorageRepository fileStorageRepository;
VsumViewMetaModelRepository vsumViewMetaModelRepository;

/**
* Creates and persists a new {@link VsumView} for the given VSUM and NeoJoin file.
Expand Down Expand Up @@ -60,11 +63,15 @@ public VsumView create(Vsum vsum, Long fileStorageId) {
/**
* Deletes the specified view and removes it from the in-memory VSUM collection if present.
*
* <p>Metamodel associations ({@code VsumViewMetaModel}) for the view are deleted before the view
* itself to satisfy foreign key constraints.
*
* @param vsum target VSUM
* @param vsumView view to delete
*/
@Transactional
public void delete(Vsum vsum, VsumView vsumView) {
vsumViewMetaModelRepository.deleteAllByVsumView(vsumView);
vsumViewRepository.delete(vsumView);

if (vsum.getViews() != null) {
Expand All @@ -75,10 +82,17 @@ public void delete(Vsum vsum, VsumView vsumView) {
/**
* Deletes all views associated with the given VSUM.
*
* <p>Metamodel associations ({@code VsumViewMetaModel}) for all views are deleted before the
* views themselves to satisfy foreign key constraints.
*
* @param vsum target VSUM
*/
@Transactional
public void deleteByVsum(Vsum vsum) {
List<VsumView> views = vsumViewRepository.findAllByVsum(vsum);
if (!views.isEmpty()) {
vsumViewMetaModelRepository.deleteAllByVsumViewIn(views);
}
vsumViewRepository.deleteAllByVsum(vsum);

if (vsum.getViews() != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ALTER TABLE vsum_view_meta_model
ADD CONSTRAINT uk_vsum_view_meta_model UNIQUE (vsum_view_id, meta_model_id);

CREATE INDEX idx_vsum_view_meta_model_vsum_view_id ON vsum_view_meta_model (vsum_view_id);

CREATE INDEX idx_vsum_view_meta_model_meta_model_id ON vsum_view_meta_model (meta_model_id);
Original file line number Diff line number Diff line change
Expand Up @@ -400,4 +400,104 @@ void revert_filtersNullRelationsWhileMapping() {

assertThat(reqCap.getValue().getMetaModelRelationRequests()).hasSize(1);
}

@Test
void revert_mapsViewsToViewRequests_whenRepresentationContainsViews() {
String callerEmail = "u@ex.com";

User user = new User();
user.setEmail(callerEmail);
when(userRepository.findByEmailIgnoreCaseAndRemovedAtIsNull(callerEmail))
.thenReturn(Optional.of(user));

Vsum vsum = new Vsum();
vsum.setId(104L);

VsumRepresentation.View view1 =
VsumRepresentation.View.builder()
.fileStorageId(10L)
.metaModelIds(List.of(1L, 2L))
.build();
VsumRepresentation.View view2 =
VsumRepresentation.View.builder()
.fileStorageId(20L)
.metaModelIds(List.of(3L))
.build();

VsumRepresentation rep =
VsumRepresentation.builder()
.metaModels(Set.of())
.metaModelsRealation(Set.of())
.views(new HashSet<>(Set.of(view1, view2)))
.build();

Long historyId = 16L;
VsumHistory history =
VsumHistory.builder().id(historyId).vsum(vsum).representation(rep).build();
when(vsumHistoryRepository.findById(historyId)).thenReturn(Optional.of(history));

when(vsumUserRepository
.findByVsum_IdAndUser_EmailAndUser_RemovedAtIsNullAndVsum_RemovedAtIsNull(
vsum.getId(), callerEmail))
.thenReturn(Optional.of(new VsumUser()));

service.revert(callerEmail, historyId);

ArgumentCaptor<VsumSyncChangesPutRequest> reqCap =
ArgumentCaptor.forClass(VsumSyncChangesPutRequest.class);
verify(vsumService).applySyncChanges(eq(vsum), eq(user), reqCap.capture(), eq(false));

VsumSyncChangesPutRequest applied = reqCap.getValue();
assertThat(applied.getViewRequests()).hasSize(2);
assertThat(applied.getViewRequests())
.anySatisfy(
v -> {
assertThat(v.getFileStorageId()).isEqualTo(10L);
assertThat(v.getMetaModelIds()).containsExactlyInAnyOrder(1L, 2L);
});
assertThat(applied.getViewRequests())
.anySatisfy(
v -> {
assertThat(v.getFileStorageId()).isEqualTo(20L);
assertThat(v.getMetaModelIds()).containsExactly(3L);
});
}

@Test
void revert_mapsNullViewsToEmptyViewRequests() {
String callerEmail = "u@ex.com";

User user = new User();
user.setEmail(callerEmail);
when(userRepository.findByEmailIgnoreCaseAndRemovedAtIsNull(callerEmail))
.thenReturn(Optional.of(user));

Vsum vsum = new Vsum();
vsum.setId(105L);

VsumRepresentation rep =
VsumRepresentation.builder()
.metaModels(null)
.metaModelsRealation(null)
.views(null)
.build();

Long historyId = 17L;
VsumHistory history =
VsumHistory.builder().id(historyId).vsum(vsum).representation(rep).build();
when(vsumHistoryRepository.findById(historyId)).thenReturn(Optional.of(history));

when(vsumUserRepository
.findByVsum_IdAndUser_EmailAndUser_RemovedAtIsNullAndVsum_RemovedAtIsNull(
vsum.getId(), callerEmail))
.thenReturn(Optional.of(new VsumUser()));

service.revert(callerEmail, historyId);

ArgumentCaptor<VsumSyncChangesPutRequest> reqCap =
ArgumentCaptor.forClass(VsumSyncChangesPutRequest.class);
verify(vsumService).applySyncChanges(eq(vsum), eq(user), reqCap.capture(), eq(false));

assertThat(reqCap.getValue().getViewRequests()).isNotNull().isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ void setUp() {

lenient().when(vsumViewRepository.findAllByVsum(any(Vsum.class))).thenReturn(List.of());
lenient()
.when(vsumViewMetaModelRepository.findAllByVsumView(any(VsumView.class)))
.when(vsumViewMetaModelRepository.findAllByVsumViewIn(anyList()))
.thenReturn(List.of());
}

Expand Down Expand Up @@ -765,9 +765,12 @@ void update_deletesViews_whenExistingViewIsRemoved_andWritesHistory() {

VsumViewMetaModel vm1 = new VsumViewMetaModel();
vm1.setMetaModel(clonedMetaModel(1001L, 1L));
vm1.setVsumView(existingView);
VsumViewMetaModel vm2 = new VsumViewMetaModel();
vm2.setMetaModel(clonedMetaModel(1002L, 2L));
when(vsumViewMetaModelRepository.findAllByVsumView(existingView)).thenReturn(List.of(vm1, vm2));
vm2.setVsumView(existingView);
when(vsumViewMetaModelRepository.findAllByVsumViewIn(List.of(existingView)))
.thenReturn(List.of(vm1, vm2));

VsumSyncChangesPutRequest put = new VsumSyncChangesPutRequest();
put.setViewRequests(List.of());
Expand Down Expand Up @@ -806,9 +809,12 @@ void update_doesNotTouchViews_whenDesiredMatchesExistingAfterNormalization() {

VsumViewMetaModel vm1 = new VsumViewMetaModel();
vm1.setMetaModel(clonedMetaModel(2001L, 1L));
vm1.setVsumView(existingView);
VsumViewMetaModel vm2 = new VsumViewMetaModel();
vm2.setMetaModel(clonedMetaModel(2002L, 2L));
when(vsumViewMetaModelRepository.findAllByVsumView(existingView)).thenReturn(List.of(vm1, vm2));
vm2.setVsumView(existingView);
when(vsumViewMetaModelRepository.findAllByVsumViewIn(List.of(existingView)))
.thenReturn(List.of(vm1, vm2));

VsumSyncChangesPutRequest put = new VsumSyncChangesPutRequest();
put.setViewRequests(
Expand Down
Loading