diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 9f3cfc94aa..48b87b97de 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -57,6 +57,9 @@ See the new interface `org.eclipse.sirius.components.emf.services.api.IRepresent - https://github.com/eclipse-sirius/sirius-web/issues/4597[#4597] [sirius-web] Add a command to publish libraries from studios The command creates a library for each _RepresentationDescription_ and _Domain_ in the studio, and creates the dependencies between them. A _shared components_ library can be created in the process to store elements that are needed by other libraries but are not stored in libraries themselves. +- https://github.com/eclipse-sirius/sirius-web/issues/4652[#4652] [sirius-web] Add support for loading projects with dependencies +Semantic data can have dependencies between them, which are transitively loaded when an editing context is created. +A new predicate `IReadOnlyObjectPredicate` has been added with a default implementation that returns `true` if the object belongs to semantic data loaded as a dependency. === Improvements diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/EditingContextDependencyLoader.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/EditingContextDependencyLoader.java new file mode 100644 index 0000000000..4e42261cd4 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/EditingContextDependencyLoader.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2025 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.editingcontext.services; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.web.application.UUIDParser; +import org.eclipse.sirius.web.application.editingcontext.EditingContext; +import org.eclipse.sirius.web.application.editingcontext.services.api.IEditingContextDependencyLoader; +import org.eclipse.sirius.web.application.editingcontext.services.api.IEditingContextMigrationParticipantPredicate; +import org.eclipse.sirius.web.application.editingcontext.services.api.IResourceLoader; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticData; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticDataDependency; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.services.api.ISemanticDataSearchService; +import org.springframework.data.jdbc.core.mapping.AggregateReference; +import org.springframework.stereotype.Service; + +/** + * Loads dependencies into the editing context. + * + * @author gdaniel + */ +@Service +public class EditingContextDependencyLoader implements IEditingContextDependencyLoader { + + private final ISemanticDataSearchService semanticDataSearchService; + + private final IResourceLoader resourceLoader; + + private final List migrationParticipantPredicates; + + public EditingContextDependencyLoader(ISemanticDataSearchService semanticDataSearchService, IResourceLoader resourceLoader, List migrationParticipantPredicates) { + this.semanticDataSearchService = Objects.requireNonNull(semanticDataSearchService); + this.resourceLoader = Objects.requireNonNull(resourceLoader); + this.migrationParticipantPredicates = Objects.requireNonNull(migrationParticipantPredicates); + } + + @Override + public void loadDependencies(IEditingContext editingContext) { + if (editingContext instanceof EditingContext siriusWebEditingContext) { + Set dependenciesSemanticData = this.getDependenciesSemanticData(siriusWebEditingContext); + for (SemanticData semanticData : dependenciesSemanticData) { + semanticData.getDocuments().forEach(document -> { + URI dependencyResourceURI = URI.createURI(DEPENDENCY_SCHEME + ":///" + document.getId().toString()); + if (siriusWebEditingContext.getDomain().getResourceSet().getResource(dependencyResourceURI, false) == null) { + Optional resource = this.resourceLoader.toResource(siriusWebEditingContext.getDomain().getResourceSet(), document.getId().toString(), document.getName(), document.getContent(), + this.migrationParticipantPredicates.stream().anyMatch(predicate -> predicate.test(editingContext.getId()))); + resource.ifPresent(r -> { + siriusWebEditingContext.getDomain().getResourceSet().getURIConverter().getURIMap().put(r.getURI(), dependencyResourceURI); + r.setURI(dependencyResourceURI); + }); + } + }); + } + } + } + + private Set getDependenciesSemanticData(EditingContext editingContext) { + Set semanticDataToLoad = new LinkedHashSet<>(); + List editingContextDependencies = new UUIDParser().parse(editingContext.getId()) + .flatMap(this.semanticDataSearchService::findById) + .map(SemanticData::getDependencies) + .orElse(List.of()) + .stream() + .map(SemanticDataDependency::dependencySemanticDataId) + .map(AggregateReference::getId) + .map(this.semanticDataSearchService::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); + + for (SemanticData editingContextDependency : editingContextDependencies) { + semanticDataToLoad.add(editingContextDependency); + semanticDataToLoad.addAll(this.semanticDataSearchService.findAllTransitiveSemanticDataById(editingContextDependency.getId())); + } + return semanticDataToLoad; + } +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/EditingContextLoader.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/EditingContextLoader.java index 9cfa9b15ae..10d7e90139 100644 --- a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/EditingContextLoader.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/EditingContextLoader.java @@ -21,6 +21,7 @@ import org.eclipse.sirius.components.emf.services.EditingContextCrossReferenceAdapter; import org.eclipse.sirius.emfjson.resource.JsonResource; import org.eclipse.sirius.web.application.editingcontext.EditingContext; +import org.eclipse.sirius.web.application.editingcontext.services.api.IEditingContextDependencyLoader; import org.eclipse.sirius.web.application.editingcontext.services.api.IEditingContextLoader; import org.eclipse.sirius.web.application.editingcontext.services.api.IEditingContextMigrationParticipantPredicate; import org.eclipse.sirius.web.application.editingcontext.services.api.IResourceLoader; @@ -41,22 +42,27 @@ public class EditingContextLoader implements IEditingContextLoader { private final IResourceLoader resourceLoader; + private final IEditingContextDependencyLoader editingContextLibraryLoader; + private final List representationDescriptionProviders; private final List editingContextProcessors; private final List migrationParticipantPredicates; - public EditingContextLoader(IResourceLoader resourceLoader, List representationDescriptionProviders, List editingContextProcessors, List migrationParticipantPredicates) { + public EditingContextLoader(IResourceLoader resourceLoader, IEditingContextDependencyLoader editingContextLibraryLoader, List representationDescriptionProviders, List editingContextProcessors, List migrationParticipantPredicates) { this.resourceLoader = Objects.requireNonNull(resourceLoader); + this.editingContextLibraryLoader = Objects.requireNonNull(editingContextLibraryLoader); this.representationDescriptionProviders = Objects.requireNonNull(representationDescriptionProviders); this.editingContextProcessors = Objects.requireNonNull(editingContextProcessors); this.migrationParticipantPredicates = Objects.requireNonNull(migrationParticipantPredicates); } + @Override public void load(EditingContext editingContext, SemanticData semanticData) { this.editingContextProcessors.forEach(processor -> processor.preProcess(editingContext)); + this.editingContextLibraryLoader.loadDependencies(editingContext); this.loadSemanticData(editingContext, semanticData); this.representationDescriptionProviders.forEach(representationDescriptionProvider -> { diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/api/IEditingContextDependencyLoader.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/api/IEditingContextDependencyLoader.java new file mode 100644 index 0000000000..90d4524b9f --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/api/IEditingContextDependencyLoader.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2025 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.editingcontext.services.api; + +import org.eclipse.sirius.components.core.api.IEditingContext; + +/** + * Loads dependencies into the editing context. + * + * @author gdaniel + */ +public interface IEditingContextDependencyLoader { + + String DEPENDENCY_SCHEME = "dependency"; + + void loadDependencies(IEditingContext editingContext); + +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/ComposedReadOnlyObjectPredicate.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/ComposedReadOnlyObjectPredicate.java new file mode 100644 index 0000000000..b14d6849cd --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/ComposedReadOnlyObjectPredicate.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2025 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.object.services; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.sirius.web.application.object.services.api.IDefaultReadOnlyObjectPredicate; +import org.eclipse.sirius.web.application.object.services.api.IReadOnlyObjectPredicate; +import org.eclipse.sirius.web.application.object.services.api.IReadOnlyObjectPredicateDelegate; +import org.springframework.stereotype.Service; + +/** + * Implementation of {@link IReadOnlyObjectPredicate} which delegates to {@link IReadOnlyObjectPredicateDelegate} or fallback to + * {@link IDefaultReadOnlyObjectPredicate}. + * + * @author gdaniel + */ +@Service +public class ComposedReadOnlyObjectPredicate implements IReadOnlyObjectPredicate { + + private final List readOnlyObjectPredicateDelegate; + + private final IDefaultReadOnlyObjectPredicate defaultReadOnlyObjectPredicate; + + public ComposedReadOnlyObjectPredicate(List readOnlyObjectPredicateDelegate, IDefaultReadOnlyObjectPredicate defaultReadOnlyObjectPredicate) { + this.readOnlyObjectPredicateDelegate = Objects.requireNonNull(readOnlyObjectPredicateDelegate); + this.defaultReadOnlyObjectPredicate = Objects.requireNonNull(defaultReadOnlyObjectPredicate); + } + + @Override + public boolean test(Object object) { + var optionalDelegate = this.readOnlyObjectPredicateDelegate.stream() + .filter(delegate -> delegate.canHandle(object)) + .findFirst(); + if (optionalDelegate.isPresent()) { + return optionalDelegate.get().test(object); + } + return this.defaultReadOnlyObjectPredicate.test(object); + } + +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/DefaultReadOnlyObjectPredicate.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/DefaultReadOnlyObjectPredicate.java new file mode 100644 index 0000000000..e8a45805b7 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/DefaultReadOnlyObjectPredicate.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2025 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.object.services; + +import java.util.Objects; +import java.util.Optional; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.sirius.web.application.editingcontext.services.api.IEditingContextDependencyLoader; +import org.eclipse.sirius.web.application.object.services.api.IDefaultReadOnlyObjectPredicate; +import org.springframework.stereotype.Service; + +/** + * The default service used to test if an object is read-only. + * + * @author gdaniel + */ +@Service +public class DefaultReadOnlyObjectPredicate implements IDefaultReadOnlyObjectPredicate { + + @Override + public boolean test(Object object) { + boolean result = false; + if (object instanceof EObject eObject) { + result = Optional.ofNullable(eObject.eResource()) + .map(Resource::getURI) + .map(URI::scheme) + .filter(scheme -> Objects.equals(scheme, IEditingContextDependencyLoader.DEPENDENCY_SCHEME)) + .isPresent(); + } else if (object instanceof Resource resource) { + result = Objects.equals(resource.getURI().scheme(), IEditingContextDependencyLoader.DEPENDENCY_SCHEME); + } + return result; + } + +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/api/IDefaultReadOnlyObjectPredicate.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/api/IDefaultReadOnlyObjectPredicate.java new file mode 100644 index 0000000000..532ddba284 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/api/IDefaultReadOnlyObjectPredicate.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2025 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.object.services.api; + +import java.util.function.Predicate; + +/** + * The default service used to test if an object is read-only. + * + * @author gdaniel + */ +public interface IDefaultReadOnlyObjectPredicate extends Predicate { + +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/api/IReadOnlyObjectPredicate.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/api/IReadOnlyObjectPredicate.java new file mode 100644 index 0000000000..c16a18bea5 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/api/IReadOnlyObjectPredicate.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2025 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.object.services.api; + +import java.util.function.Predicate; + +/** + * Used to test if an object is read-only. + * + * @author gdaniel + */ +public interface IReadOnlyObjectPredicate extends Predicate { + +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/api/IReadOnlyObjectPredicateDelegate.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/api/IReadOnlyObjectPredicateDelegate.java new file mode 100644 index 0000000000..2562fd1417 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/object/services/api/IReadOnlyObjectPredicateDelegate.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2025 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.object.services.api; + +import java.util.function.Predicate; + +/** + * Used to test if an object is read-only. + * + * @author gdaniel + */ +public interface IReadOnlyObjectPredicateDelegate extends Predicate { + + boolean canHandle(Object object); + +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerServices.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerServices.java index 83a7135d64..a73c3a8753 100644 --- a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerServices.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerServices.java @@ -33,6 +33,7 @@ import org.eclipse.sirius.components.emf.services.JSONResourceFactory; import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext; import org.eclipse.sirius.web.application.UUIDParser; +import org.eclipse.sirius.web.application.object.services.api.IReadOnlyObjectPredicate; import org.eclipse.sirius.web.application.views.explorer.services.api.IExplorerServices; import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.RepresentationIconURL; import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.RepresentationMetadata; @@ -56,11 +57,14 @@ public class ExplorerServices implements IExplorerServices { private final IRepresentationMetadataSearchService representationMetadataSearchService; - public ExplorerServices(IObjectService objectService, IURLParser urlParser, List representationImageProviders, IRepresentationMetadataSearchService representationMetadataSearchService) { + private final IReadOnlyObjectPredicate readOnlyObjectPredicate; + + public ExplorerServices(IObjectService objectService, IURLParser urlParser, List representationImageProviders, IRepresentationMetadataSearchService representationMetadataSearchService, IReadOnlyObjectPredicate readOnlyObjectPredicate) { this.objectService = Objects.requireNonNull(objectService); this.urlParser = Objects.requireNonNull(urlParser); this.representationImageProviders = Objects.requireNonNull(representationImageProviders); this.representationMetadataSearchService = Objects.requireNonNull(representationMetadataSearchService); + this.readOnlyObjectPredicate = Objects.requireNonNull(readOnlyObjectPredicate); } @Override @@ -114,19 +118,21 @@ private String getResourceLabel(Resource resource) { @Override public boolean isEditable(Object self) { boolean editable = false; - if (self instanceof RepresentationMetadata) { - editable = true; - } else if (self instanceof Resource) { - editable = true; - } else if (self instanceof EObject) { - editable = this.objectService.isLabelEditable(self); + if (!this.readOnlyObjectPredicate.test(self)) { + if (self instanceof RepresentationMetadata) { + editable = true; + } else if (self instanceof Resource) { + editable = true; + } else if (self instanceof EObject) { + editable = this.objectService.isLabelEditable(self); + } } return editable; } @Override public boolean isDeletable(Object self) { - return true; + return !this.readOnlyObjectPredicate.test(self); } @Override diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/library/services/LibrarySearchService.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/library/services/LibrarySearchService.java index 78059cacb0..c2893729eb 100644 --- a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/library/services/LibrarySearchService.java +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/library/services/LibrarySearchService.java @@ -50,4 +50,5 @@ public boolean existsByNamespaceAndNameAndVersion(String namespace, String name, public Optional findByNamespaceAndNameAndVersion(String namespace, String name, String version) { return this.libraryRepository.findByNamespaceAndNameAndVersion(namespace, name, version); } + } diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/library/services/api/ILibrarySearchService.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/library/services/api/ILibrarySearchService.java index 54325a900d..05189d8d01 100644 --- a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/library/services/api/ILibrarySearchService.java +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/library/services/api/ILibrarySearchService.java @@ -30,4 +30,5 @@ public interface ILibrarySearchService { boolean existsByNamespaceAndNameAndVersion(String namespace, String name, String version); Optional findByNamespaceAndNameAndVersion(String namespace, String name, String version); + } diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/SemanticData.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/SemanticData.java index 80cfb823a5..afcfaf068b 100644 --- a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/SemanticData.java +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/SemanticData.java @@ -25,6 +25,8 @@ import org.eclipse.sirius.components.events.ICause; import org.eclipse.sirius.web.domain.boundedcontexts.AbstractValidatingAggregateRoot; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.events.SemanticDataCreatedEvent; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.events.SemanticDataDependencyAddedEvent; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.events.SemanticDataDependencyRemovedEvent; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.events.SemanticDataUpdatedEvent; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; @@ -77,6 +79,27 @@ public List getDependencies() { return Collections.unmodifiableList(this.dependencies); } + public void addDependency(ICause cause, UUID semanticDataId) { + var newDependency = new SemanticDataDependency(AggregateReference.to(semanticDataId)); + this.dependencies.add(newDependency); + this.lastModifiedOn = Instant.now(); + + this.registerEvent(new SemanticDataDependencyAddedEvent(UUID.randomUUID(), this.lastModifiedOn, cause, this, newDependency)); + + } + + public void removeDependency(ICause cause, UUID semanticDataId) { + this.dependencies.stream() + .filter(dependency -> dependency.dependencySemanticDataId().getId().equals(semanticDataId)) + .findFirst() + .ifPresent(dependency -> { + this.dependencies.remove(dependency); + this.lastModifiedOn = Instant.now(); + + this.registerEvent(new SemanticDataDependencyRemovedEvent(UUID.randomUUID(), this.lastModifiedOn, cause, this, dependency)); + }); + } + public Instant getCreatedOn() { return this.createdOn; } @@ -151,6 +174,20 @@ public static Builder newSemanticData() { return new Builder(); } + @Override + public boolean equals(Object obj) { + boolean result = false; + if (obj instanceof SemanticData semanticData) { + result = Objects.equals(this.id, semanticData.getId()); + } + return result; + } + + @Override + public int hashCode() { + return this.id.hashCode(); + } + /** * Used to create new semantic data. * diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/events/SemanticDataDependencyAddedEvent.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/events/SemanticDataDependencyAddedEvent.java new file mode 100644 index 0000000000..c64830b1ab --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/events/SemanticDataDependencyAddedEvent.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2025 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.semanticdata.events; + +import java.time.Instant; +import java.util.UUID; + +import org.eclipse.sirius.components.events.ICause; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticData; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticDataDependency; + +import jakarta.validation.constraints.NotNull; + +/** + * Event fired when a dependency is added. + * + * @author gdaniel + */ +public record SemanticDataDependencyAddedEvent( + @NotNull UUID id, + @NotNull Instant createdOn, + @NotNull ICause causedBy, + @NotNull SemanticData semanticData, + @NotNull SemanticDataDependency dependency) implements ISemanticDataEvent { +} diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/events/SemanticDataDependencyRemovedEvent.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/events/SemanticDataDependencyRemovedEvent.java new file mode 100644 index 0000000000..f6a897f0a0 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/events/SemanticDataDependencyRemovedEvent.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2025 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.semanticdata.events; + +import java.time.Instant; +import java.util.UUID; + +import org.eclipse.sirius.components.events.ICause; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticData; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticDataDependency; + +import jakarta.validation.constraints.NotNull; + +/** + * Event fired when a dependency is removed. + * + * @author gdaniel + */ +public record SemanticDataDependencyRemovedEvent( + @NotNull UUID id, + @NotNull Instant createdOn, + @NotNull ICause causedBy, + @NotNull SemanticData semanticData, + @NotNull SemanticDataDependency dependency) implements ISemanticDataEvent { +} diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/SemanticDataSearchService.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/SemanticDataSearchService.java index 65a334264f..72cd80e7b9 100644 --- a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/SemanticDataSearchService.java +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/SemanticDataSearchService.java @@ -12,14 +12,18 @@ *******************************************************************************/ package org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.services; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticData; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticDataDependency; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.repositories.ISemanticDataRepository; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.services.api.ISemanticDataSearchService; +import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.stereotype.Service; /** @@ -50,4 +54,29 @@ public Optional findById(UUID id) { public boolean existsById(UUID id) { return this.semanticDataRepository.existsById(id); } + + @Override + public Set findAllTransitiveSemanticDataById(UUID id) { + Set collectedSemanticData = new LinkedHashSet<>(); + Optional semanticData = this.findById(id); + if (semanticData.isPresent()) { + this.collectTransitiveSemanticData(semanticData.get(), collectedSemanticData); + } + return collectedSemanticData; + } + + private void collectTransitiveSemanticData(SemanticData semanticData, Set collectedSemanticData) { + if (!collectedSemanticData.contains(semanticData)) { + semanticData.getDependencies().stream() + .map(SemanticDataDependency::dependencySemanticDataId) + .map(AggregateReference::getId) + .map(this::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(dependencySemanticData -> { + collectedSemanticData.add(semanticData); + this.collectTransitiveSemanticData(dependencySemanticData, collectedSemanticData); + }); + } + } } diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/SemanticDataUpdateService.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/SemanticDataUpdateService.java index 846e321c9c..fe0c6e33bc 100644 --- a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/SemanticDataUpdateService.java +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/SemanticDataUpdateService.java @@ -21,6 +21,10 @@ import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticData; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.repositories.ISemanticDataRepository; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.services.api.ISemanticDataUpdateService; +import org.eclipse.sirius.web.domain.services.Failure; +import org.eclipse.sirius.web.domain.services.IResult; +import org.eclipse.sirius.web.domain.services.Success; +import org.eclipse.sirius.web.domain.services.api.IMessageService; import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.stereotype.Service; @@ -34,8 +38,11 @@ public class SemanticDataUpdateService implements ISemanticDataUpdateService { private final ISemanticDataRepository semanticDataRepository; - public SemanticDataUpdateService(ISemanticDataRepository semanticDataRepository) { + private final IMessageService messageService; + + public SemanticDataUpdateService(ISemanticDataRepository semanticDataRepository, IMessageService messageService) { this.semanticDataRepository = Objects.requireNonNull(semanticDataRepository); + this.messageService = Objects.requireNonNull(messageService); } @Override @@ -45,4 +52,38 @@ public void updateDocuments(ICause cause, AggregateReference this.semanticDataRepository.save(semanticData); }); } + + @Override + public IResult addDependency(ICause cause, UUID semanticDataId, UUID dependencySemanticDataId) { + IResult result = null; + + var optionalSemanticData = this.semanticDataRepository.findById(semanticDataId); + if (optionalSemanticData.isEmpty()) { + result = new Failure<>(this.messageService.notFound()); + } else { + var semanticData = optionalSemanticData.get(); + semanticData.addDependency(cause, dependencySemanticDataId); + this.semanticDataRepository.save(semanticData); + result = new Success<>(null); + } + + return result; + } + + @Override + public IResult removeDependency(ICause cause, UUID semanticDataId, UUID dependencySemanticDataId) { + IResult result = null; + + var optionalSemanticData = this.semanticDataRepository.findById(semanticDataId); + if (optionalSemanticData.isEmpty()) { + result = new Failure<>(this.messageService.notFound()); + } else { + var semanticData = optionalSemanticData.get(); + semanticData.removeDependency(cause, dependencySemanticDataId); + this.semanticDataRepository.save(semanticData); + result = new Success<>(null); + } + + return result; + } } diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/api/ISemanticDataSearchService.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/api/ISemanticDataSearchService.java index a85eb7ba00..4f8fe07b29 100644 --- a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/api/ISemanticDataSearchService.java +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/api/ISemanticDataSearchService.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticData; @@ -30,4 +31,6 @@ public interface ISemanticDataSearchService { Optional findById(UUID id); boolean existsById(UUID id); + + Set findAllTransitiveSemanticDataById(UUID id); } diff --git a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/api/ISemanticDataUpdateService.java b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/api/ISemanticDataUpdateService.java index 4af5151bf1..6150001e6d 100644 --- a/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/api/ISemanticDataUpdateService.java +++ b/packages/sirius-web/backend/sirius-web-domain/src/main/java/org/eclipse/sirius/web/domain/boundedcontexts/semanticdata/services/api/ISemanticDataUpdateService.java @@ -18,6 +18,7 @@ import org.eclipse.sirius.components.events.ICause; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.Document; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticData; +import org.eclipse.sirius.web.domain.services.IResult; import org.springframework.data.jdbc.core.mapping.AggregateReference; /** @@ -27,4 +28,9 @@ */ public interface ISemanticDataUpdateService { void updateDocuments(ICause cause, AggregateReference semanticData, Set documents, Set domainUris); + + IResult addDependency(ICause cause, UUID semanticDataId, UUID dependencySemanticDataId); + + IResult removeDependency(ICause cause, UUID semanticDataID, UUID dependencySemanticDataId); + } diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/libraries/LibraryControllerIntegrationTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/libraries/LibraryControllerIntegrationTests.java index f295252767..cbf39db716 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/libraries/LibraryControllerIntegrationTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/libraries/LibraryControllerIntegrationTests.java @@ -29,7 +29,6 @@ import org.eclipse.sirius.web.domain.boundedcontexts.library.services.api.ILibrarySearchService; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.SemanticDataDependency; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.services.api.ISemanticDataSearchService; -import org.eclipse.sirius.web.papaya.services.library.api.IStandardLibrarySemanticDataInitializer; import org.eclipse.sirius.web.tests.data.GivenSiriusWebServer; import org.eclipse.sirius.web.tests.graphql.LibrariesQueryRunner; import org.eclipse.sirius.web.tests.graphql.PublishLibrariesMutationRunner; @@ -48,6 +47,7 @@ * @author gdaniel */ @Transactional +@SuppressWarnings("checkstyle:MultipleStringLiterals") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class LibraryControllerIntegrationTests extends AbstractIntegrationTests { @@ -63,9 +63,6 @@ public class LibraryControllerIntegrationTests extends AbstractIntegrationTests @Autowired private ISemanticDataSearchService semanticDataSearchService; - @Autowired - private IStandardLibrarySemanticDataInitializer standardLibrarySemanticDataInitializer; - @Test @GivenSiriusWebServer @DisplayName("Given a set of libraries, when a query is performed, then the libraries are returned") diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/services/EditingContextLifecycleTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/services/EditingContextLifecycleTests.java index ed0cf107e0..06f416be6c 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/services/EditingContextLifecycleTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/services/EditingContextLifecycleTests.java @@ -15,14 +15,25 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import java.util.Objects; +import java.util.Optional; + import org.eclipse.emf.ecore.EPackage; import org.eclipse.sirius.components.core.api.IEditingContextPersistenceService; import org.eclipse.sirius.components.core.api.IEditingContextSearchService; +import org.eclipse.sirius.components.core.api.ILabelService; import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext; import org.eclipse.sirius.components.events.ICause; import org.eclipse.sirius.web.AbstractIntegrationTests; import org.eclipse.sirius.web.application.editingcontext.EditingContext; +import org.eclipse.sirius.web.application.editingcontext.services.api.IEditingContextDependencyLoader; +import org.eclipse.sirius.web.application.object.services.api.IReadOnlyObjectPredicate; +import org.eclipse.sirius.web.data.PapayaIdentifiers; import org.eclipse.sirius.web.data.TestIdentifiers; +import org.eclipse.sirius.web.domain.boundedcontexts.library.Library; +import org.eclipse.sirius.web.domain.boundedcontexts.library.services.api.ILibrarySearchService; +import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.services.api.ISemanticDataUpdateService; +import org.eclipse.sirius.web.domain.services.Success; import org.eclipse.sirius.web.tests.data.GivenSiriusWebServer; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -45,6 +56,18 @@ public class EditingContextLifecycleTests extends AbstractIntegrationTests { @Autowired private IEditingContextPersistenceService editingContextPersistenceService; + @Autowired + private ILibrarySearchService librarySearchService; + + @Autowired + private ISemanticDataUpdateService semanticDataUpdateService; + + @Autowired + private ILabelService labelService; + + @Autowired + private IReadOnlyObjectPredicate readOnlyObjectPredicate; + @Test @GivenSiriusWebServer @DisplayName("Given semantic data using static metamodels, when the loading is performed, then the semantic data are available in the editing context") @@ -115,4 +138,34 @@ public void givenEditingContextProperlyLoadedWhenItIsModifiedAndPersistedThenThe assertThat(ePackage.getName()).isEqualTo("Sample Updated"); } } + + @Test + @GivenSiriusWebServer + @DisplayName("Given semantic data with a dependency, when the loading is performed, then the semantic data are available in the editing context") + public void givenSemanticDataWithDependencyWhenLoadingIsPerformedThenSemanticDataAvailableInEditingContext() { + Optional library = this.librarySearchService.findByNamespaceAndNameAndVersion("java", "Java Standard Library", "17.0.0"); + assertThat(library).isPresent(); + var result = this.semanticDataUpdateService.addDependency(null, PapayaIdentifiers.PAPAYA_EDITING_CONTEXT_ID, library.get().getSemanticData().getId()); + assertThat(result).isInstanceOf(Success.class); + TestTransaction.flagForCommit(); + TestTransaction.end(); + TestTransaction.start(); + + var optionalEditingContext = this.editingContextSearchService.findById(PapayaIdentifiers.PAPAYA_EDITING_CONTEXT_ID.toString()); + assertThat(optionalEditingContext).isPresent(); + + var editingContext = optionalEditingContext.get(); + assertThat(editingContext.getId()).isEqualTo(PapayaIdentifiers.PAPAYA_EDITING_CONTEXT_ID.toString()); + if (editingContext instanceof EditingContext siriusWebEditingContext) { + var resourceSet = siriusWebEditingContext.getDomain().getResourceSet(); + assertThat(resourceSet.getResources()) + .hasSize(3) + .anyMatch(resource -> + this.labelService.getLabel(resource).equals("Java Standard Library") + && Objects.equals(resource.getURI().scheme(), IEditingContextDependencyLoader.DEPENDENCY_SCHEME) + && this.readOnlyObjectPredicate.test(resource)); + } else { + fail("Invalid editing context"); + } + } }