From 8cb6686b68fbba329bff9590ad2f196de14a4e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20B=C3=A9gaudeau?= Date: Tue, 25 Jun 2024 09:28:08 +0200 Subject: [PATCH] [3630] Restore support for the deletion of dangling representations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://github.com/eclipse-sirius/sirius-web/issues/3630 Signed-off-by: Stéphane Bégaudeau --- CHANGELOG.adoc | 1 + ...DanglingRepresentationDeletionService.java | 6 +- .../EditingContextEventProcessor.java | 2 +- ...DanglingRepresentationDeletionService.java | 24 +++- .../RepresentationDataMetadataOnly.java | 28 +++++ .../IRepresentationDataRepository.java | 8 ++ .../RepresentationDataSearchService.java | 6 + .../api/IRepresentationDataSearchService.java | 3 + .../RepresentationService.java | 4 +- ...resentationControllerIntegrationTests.java | 116 ++++++++++++++++++ .../trees/TreeControllerIntegrationTests.java | 8 -- 11 files changed, 189 insertions(+), 17 deletions(-) create mode 100644 packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/projections/RepresentationDataMetadataOnly.java create mode 100644 packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/representations/DanglingRepresentationControllerIntegrationTests.java diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index fa0cb0d46a..3616c18817 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -65,6 +65,7 @@ More existing APIs will be migrated to this new common pattern. - https://github.com/eclipse-sirius/sirius-web/issues/3531[#3531] [diagram] Fix unnecessary edges label re render - https://github.com/eclipse-sirius/sirius-web/issues/3650[#3650] [diagram] Fix potential NPE in DiagramNavigator and Node toString method - https://github.com/eclipse-sirius/sirius-web/issues/3649[#3649] [sirius-web] Restore support for Related elements view icons +- https://github.com/eclipse-sirius/sirius-web/issues/3630[#3630] [sirius-web] Restore support for the deletion of dangling representations === New Features diff --git a/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/api/IDanglingRepresentationDeletionService.java b/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/api/IDanglingRepresentationDeletionService.java index 4fa0dc66c2..28f39cbd67 100644 --- a/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/api/IDanglingRepresentationDeletionService.java +++ b/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/api/IDanglingRepresentationDeletionService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021 Obeo. + * Copyright (c) 2021, 2024 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -35,7 +35,7 @@ public interface IDanglingRepresentationDeletionService { */ boolean isDangling(IEditingContext editingContext, IRepresentation representation); - void deleteDanglingRepresentations(String editingContextId); + void deleteDanglingRepresentations(IEditingContext editingContext); /** * Implementation which does nothing, used for mocks in unit tests. @@ -50,7 +50,7 @@ public boolean isDangling(IEditingContext editingContext, IRepresentation repres } @Override - public void deleteDanglingRepresentations(String editingContextId) { + public void deleteDanglingRepresentations(IEditingContext editingContext) { } } diff --git a/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessor.java b/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessor.java index 750876bdbc..ee1632c212 100644 --- a/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessor.java +++ b/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessor.java @@ -174,7 +174,7 @@ private Disposable setupChangeDescriptionSinkConsumer() { if (this.shouldPersistTheEditingContext(changeDescription)) { this.editingContextPersistenceService.persist(this.editingContext); } - this.danglingRepresentationDeletionService.deleteDanglingRepresentations(this.editingContext.getId()); + this.danglingRepresentationDeletionService.deleteDanglingRepresentations(this.editingContext); var timer = this.meterRegistry.timer(Monitoring.TIMER_REFRESH_REPRESENTATION, "changeDescription", changeDescription.getSourceId()); refreshRepresentationSample.stop(timer); diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/representation/services/DanglingRepresentationDeletionService.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/representation/services/DanglingRepresentationDeletionService.java index a45d63c119..30441d07de 100644 --- a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/representation/services/DanglingRepresentationDeletionService.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/representation/services/DanglingRepresentationDeletionService.java @@ -19,7 +19,13 @@ import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.core.api.IObjectSearchService; import org.eclipse.sirius.components.representations.IRepresentation; +import org.eclipse.sirius.web.application.UUIDParser; +import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.services.api.IRepresentationDataDeletionService; +import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.services.api.IRepresentationDataSearchService; +import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.projections.RepresentationDataMetadataOnly; +import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; /** * Used to delete dangling representations. @@ -31,8 +37,14 @@ public class DanglingRepresentationDeletionService implements IDanglingRepresent private final IObjectSearchService objectSearchService; - public DanglingRepresentationDeletionService(IObjectSearchService objectSearchService) { + private final IRepresentationDataSearchService representationDataSearchService; + + private final IRepresentationDataDeletionService representationDataDeletionService; + + public DanglingRepresentationDeletionService(IObjectSearchService objectSearchService, IRepresentationDataSearchService representationDataSearchService, IRepresentationDataDeletionService representationDataDeletionService) { this.objectSearchService = Objects.requireNonNull(objectSearchService); + this.representationDataSearchService = Objects.requireNonNull(representationDataSearchService); + this.representationDataDeletionService = Objects.requireNonNull(representationDataDeletionService); } @Override @@ -43,7 +55,13 @@ public boolean isDangling(IEditingContext editingContext, IRepresentation repres } @Override - public void deleteDanglingRepresentations(String editingContextId) { - // Do nothing for now + @Transactional + public void deleteDanglingRepresentations(IEditingContext editingContext) { + new UUIDParser().parse(editingContext.getId()).ifPresent(projectId -> { + this.representationDataSearchService.findAllMetadataByProject(AggregateReference.to(projectId)).stream() + .filter(representationMetadata -> this.objectSearchService.getObject(editingContext, representationMetadata.targetObjectId()).isEmpty()) + .map(RepresentationDataMetadataOnly::id) + .forEach(this.representationDataDeletionService::delete); + }); } } diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/projections/RepresentationDataMetadataOnly.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/projections/RepresentationDataMetadataOnly.java new file mode 100644 index 0000000000..32f1fbab5b --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/projections/RepresentationDataMetadataOnly.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2024, 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.web.domain.boundedcontexts.representationdata.projections; + +import java.util.UUID; + +/** + * Projection used to retrieve only the representation metadata of the representation data. + * + * @author sbegaudeau + */ +public record RepresentationDataMetadataOnly( + UUID id, + String label, + String kind, + String targetObjectId, + String descriptionId) { +} diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/repositories/IRepresentationDataRepository.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/repositories/IRepresentationDataRepository.java index a53f80b6d4..e9b47ac87c 100644 --- a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/repositories/IRepresentationDataRepository.java +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/repositories/IRepresentationDataRepository.java @@ -17,6 +17,7 @@ import java.util.UUID; import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.RepresentationData; +import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.projections.RepresentationDataMetadataOnly; import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.repository.ListCrudRepository; import org.springframework.data.repository.ListPagingAndSortingRepository; @@ -36,6 +37,13 @@ public interface IRepresentationDataRepository extends ListPagingAndSortingRepos """) List findAllByProjectId(UUID projectId); + @Query(""" + SELECT id, label, kind, target_object_id, description_id + FROM representation_data representationData + WHERE representationData.project_id = :projectId + """) + List findAllMetadataByProjectId(UUID projectId); + @Query(""" SELECT representationData.project_id FROM representation_data representationData diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/services/RepresentationDataSearchService.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/services/RepresentationDataSearchService.java index afdf2d641a..673d662f93 100644 --- a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/services/RepresentationDataSearchService.java +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/services/RepresentationDataSearchService.java @@ -19,6 +19,7 @@ import org.eclipse.sirius.web.domain.boundedcontexts.project.Project; import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.RepresentationData; +import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.projections.RepresentationDataMetadataOnly; import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.repositories.IRepresentationDataRepository; import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.services.api.IRepresentationDataSearchService; import org.springframework.data.jdbc.core.mapping.AggregateReference; @@ -53,6 +54,11 @@ public List findAllByProject(AggregateReference findAllMetadataByProject(AggregateReference project) { + return this.representationDataRepository.findAllMetadataByProjectId(project.getId()); + } + @Override public boolean existAnyRepresentationForTargetObjectId(String targetObjectId) { return this.representationDataRepository.existAnyRepresentationForTargetObjectId(targetObjectId); diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/services/api/IRepresentationDataSearchService.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/services/api/IRepresentationDataSearchService.java index e59ee368ac..289e4f6022 100644 --- a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/services/api/IRepresentationDataSearchService.java +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/representationdata/services/api/IRepresentationDataSearchService.java @@ -18,6 +18,7 @@ import org.eclipse.sirius.web.domain.boundedcontexts.project.Project; import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.RepresentationData; +import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.projections.RepresentationDataMetadataOnly; import org.springframework.data.jdbc.core.mapping.AggregateReference; /** @@ -33,6 +34,8 @@ public interface IRepresentationDataSearchService { List findAllByProject(AggregateReference project); + List findAllMetadataByProject(AggregateReference project); + boolean existAnyRepresentationForTargetObjectId(String targetObjectId); List findAllByTargetObjectId(String targetObjectId); diff --git a/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/representations/RepresentationService.java b/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/representations/RepresentationService.java index d64a1137a9..77bd5c6e6f 100644 --- a/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/representations/RepresentationService.java +++ b/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/representations/RepresentationService.java @@ -171,8 +171,8 @@ public boolean isDangling(IEditingContext editingContext, IRepresentation repres } @Override - public void deleteDanglingRepresentations(String editingContextId) { - new IDParser().parse(editingContextId).ifPresent(this.representationRepository::deleteDanglingRepresentations); + public void deleteDanglingRepresentations(IEditingContext editingContext) { + new IDParser().parse(editingContext.getId()).ifPresent(this.representationRepository::deleteDanglingRepresentations); } @Override diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/representations/DanglingRepresentationControllerIntegrationTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/representations/DanglingRepresentationControllerIntegrationTests.java new file mode 100644 index 0000000000..a18b9d2aa0 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/representations/DanglingRepresentationControllerIntegrationTests.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.web.application.controllers.representations; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.Optional; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Predicate; + +import org.eclipse.sirius.components.collaborative.api.ChangeDescription; +import org.eclipse.sirius.components.collaborative.api.ChangeKind; +import org.eclipse.sirius.components.collaborative.portals.dto.PortalEventInput; +import org.eclipse.sirius.components.collaborative.portals.dto.PortalRefreshedEventPayload; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IInput; +import org.eclipse.sirius.components.core.api.IPayload; +import org.eclipse.sirius.components.core.api.SuccessPayload; +import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext; +import org.eclipse.sirius.components.graphql.tests.ExecuteEditingContextFunctionInput; +import org.eclipse.sirius.components.graphql.tests.ExecuteEditingContextFunctionRunner; +import org.eclipse.sirius.components.portals.tests.graphql.PortalEventSubscriptionRunner; +import org.eclipse.sirius.web.AbstractIntegrationTests; +import org.eclipse.sirius.web.data.TestIdentifiers; +import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.transaction.TestTransaction; +import org.springframework.transaction.annotation.Transactional; + +import graphql.execution.DataFetcherResult; +import reactor.test.StepVerifier; + +/** + * Used to test the proper deletion of dangling representations. + * + * @author sbegaudeau + */ +@Transactional +@SuppressWarnings("checkstyle:MultipleStringLiterals") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class DanglingRepresentationControllerIntegrationTests extends AbstractIntegrationTests { + + @Autowired + private IGivenInitialServerState givenInitialServerState; + + @Autowired + private PortalEventSubscriptionRunner portalEventSubscriptionRunner; + + @Autowired + private ExecuteEditingContextFunctionRunner executeEditingContextFunctionRunner; + + @BeforeEach + public void beforeEach() { + this.givenInitialServerState.initialize(); + } + + @Test + @DisplayName("Given a project, when we delete an object with a child representation, then the representation is deleted") + @Sql(scripts = { "/scripts/initialize.sql" }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + public void givenProjectWhenWeDeleteAnObjectWithChildRepresentationThenTheRepresentationIsDeleted() { + var portalEventInput = new PortalEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), TestIdentifiers.EPACKAGE_PORTAL_REPRESENTATION.toString()); + var portalFlux = this.portalEventSubscriptionRunner.run(portalEventInput); + + Predicate portalContentMatcher = object -> Optional.of(object) + .filter(DataFetcherResult.class::isInstance) + .map(DataFetcherResult.class::cast) + .map(DataFetcherResult::getData) + .filter(PortalRefreshedEventPayload.class::isInstance) + .isPresent(); + + Runnable deleteProjectContent = () -> { + BiFunction function = (editingContext, input) -> { + if (editingContext instanceof IEMFEditingContext emfEditingContext) { + emfEditingContext.getDomain().getResourceSet().getResources().forEach(resource -> resource.getContents().clear()); + } + return new SuccessPayload(input.id()); + }; + + var inputId = UUID.randomUUID(); + var changeDescription = new ChangeDescription(ChangeKind.SEMANTIC_CHANGE, TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), () -> inputId); + var input = new ExecuteEditingContextFunctionInput(inputId, TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), function, changeDescription); + var payload = this.executeEditingContextFunctionRunner.execute(input).block(); + + assertThat(payload).isInstanceOf(SuccessPayload.class); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + TestTransaction.start(); + }; + + StepVerifier.create(portalFlux) + .expectNextMatches(portalContentMatcher) + .then(deleteProjectContent) + .expectComplete() + .verify(Duration.ofSeconds(10)); + } +} diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeControllerIntegrationTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeControllerIntegrationTests.java index 88f3c90af7..24b4b2c936 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeControllerIntegrationTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/TreeControllerIntegrationTests.java @@ -52,8 +52,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.jdbc.Sql; @@ -146,8 +144,6 @@ private record TreeItemMatcher(Function treeItemFinder, Predicat @Autowired private IIdentityService identityService; - private final Logger logger = LoggerFactory.getLogger(TreeControllerIntegrationTests.class); - @BeforeEach public void beforeEach() { this.givenInitialServerState.initialize(); @@ -240,8 +236,6 @@ public void givenExplorerOfProjectWhenWeRenameTreeItemThenTheTreeIsRefreshed() { var portalFlux = this.portalEventSubscriptionRunner.run(portalEventInput); Predicate portalRefreshedEventPayloadMatcher = object -> { - this.logger.info("portal refreshed event payload matcher"); - this.logger.info(object.toString()); return Optional.of(object) .filter(DataFetcherResult.class::isInstance) .map(DataFetcherResult.class::cast) @@ -298,8 +292,6 @@ private Predicate getTreeRefreshedEventPayloadMatcher(List { - this.logger.info("tree refreshed event payload matcher"); - this.logger.info(object.toString()); return Optional.of(object) .filter(DataFetcherResult.class::isInstance) .map(DataFetcherResult.class::cast)