From 5a1b805b636448cd41c3bfab74af06349f8c7fb1 Mon Sep 17 00:00:00 2001 From: Michael Charfadi Date: Thu, 4 Apr 2024 17:15:37 +0200 Subject: [PATCH] [3333] Add IMigrationParticipant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://github.com/eclipse-sirius/sirius-web/issues/3333 Signed-off-by: Michaƫl Charfadi --- CHANGELOG.adoc | 2 + .../emf/backend/sirius-components-emf/pom.xml | 2 +- .../sirius/components/emf/MigrationData.java | 31 +++++ .../emf/ResourceMetadataAdapter.java | 12 +- .../migration/IMigrationParticipant.java | 128 ++++++++++++++++++ .../migration/MigrationExtendedMetaData.java | 92 +++++++++++++ .../migration/MigrationJsonProcessor.java | 117 ++++++++++++++++ ...onLabelExpressionMigrationParticipant.java | 66 +++++++++ .../services/ResourceLoader.java | 37 ++++- .../services/ResourceToDocumentService.java | 26 ++++ .../sirius/web/data/MigrationIdentifiers.java | 31 +++++ .../migration/MigrationParticipantTests.java | 72 ++++++++++ .../src/test/resources/scripts/migration.sql | 119 ++++++++++++++++ 13 files changed, 731 insertions(+), 4 deletions(-) create mode 100644 packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/MigrationData.java create mode 100644 packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/IMigrationParticipant.java create mode 100644 packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/MigrationExtendedMetaData.java create mode 100644 packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/MigrationJsonProcessor.java create mode 100644 packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/participants/NodeDescriptionLabelExpressionMigrationParticipant.java create mode 100644 packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/data/MigrationIdentifiers.java create mode 100644 packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/migration/MigrationParticipantTests.java create mode 100644 packages/sirius-web/backend/sirius-web/src/test/resources/scripts/migration.sql diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index af8de567ee..b385588e1d 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -82,6 +82,7 @@ image:doc/screenshots/inside_outside_labels.png[Isinde outside label example, 70 - https://github.com/eclipse-sirius/sirius-web/issues/3237[#3237] [diagram] Add a `DiagramServices` class containing services to expand and collapse nodes from AQL expressions (via the new `diagramServices` variable) and Java services. - https://github.com/eclipse-sirius/sirius-web/issues/3282[#3282] [diagram] Add diagram assertions and navigators to ease the definition of diagram tests. - https://github.com/eclipse-sirius/sirius-web/issues/3277[#3277] [gantt] Add support for dependency creation +- https://github.com/eclipse-sirius/sirius-web/issues/3333[#3333] [sirius-web] Added IMigrationParticipant interface, contribute implemention to migrate models === Improvements @@ -99,6 +100,7 @@ They still support returning an `java.time.Instant` object directly. - https://github.com/eclipse-sirius/sirius-web/issues/3346[#3346] [workbench] Make the _Explorer_ panel wider by default. - https://github.com/eclipse-sirius/sirius-web/issues/3313[#3313] [core] Avoid sending events for no-op updates of domain objects - https://github.com/eclipse-sirius/sirius-web/issues/3362[#3362] [diagram] Ignore edge during multi-selection to apply distribution tools on nodes +- https://github.com/eclipse-sirius/sirius-web/issues/3333[#3333] [sirius-web] Store the last migration performed in a document == v2024.3.0 diff --git a/packages/emf/backend/sirius-components-emf/pom.xml b/packages/emf/backend/sirius-components-emf/pom.xml index 87791a7f9e..e7f9d71d07 100644 --- a/packages/emf/backend/sirius-components-emf/pom.xml +++ b/packages/emf/backend/sirius-components-emf/pom.xml @@ -102,7 +102,7 @@ org.eclipse.sirius.emfjson org.eclipse.sirius.emfjson - 2.3.7-SNAPSHOT + 2.3.8-SNAPSHOT org.springframework.boot diff --git a/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/MigrationData.java b/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/MigrationData.java new file mode 100644 index 0000000000..505b13df14 --- /dev/null +++ b/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/MigrationData.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * 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.components.emf; + +/** + * POJO for MigrationData. + * + * @author mcharfadi + */ +public record MigrationData(String lastMigrationPerformed, String migrationVersion) { + + public static final String JSON_OBJECT_ROOT = "migration"; + + @Override + public String toString() { + return "MigrationData{" + + "lastMigrationPerformed='" + lastMigrationPerformed + '\'' + + ", migrationVersion='" + migrationVersion + '\'' + + '}'; + } +} diff --git a/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/ResourceMetadataAdapter.java b/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/ResourceMetadataAdapter.java index 37f7cc21a4..4f22553c9d 100644 --- a/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/ResourceMetadataAdapter.java +++ b/packages/emf/backend/sirius-components-emf/src/main/java/org/eclipse/sirius/components/emf/ResourceMetadataAdapter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2023 Obeo. + * Copyright (c) 2019, 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 @@ -33,6 +33,8 @@ public class ResourceMetadataAdapter implements Adapter { private String name; + private MigrationData migrationData; + private Notifier notifier; public ResourceMetadataAdapter(String name) { @@ -47,6 +49,14 @@ public void setName(String name) { this.name = name; } + public MigrationData getMigrationData() { + return migrationData; + } + + public void setMigrationData(MigrationData migrationData) { + this.migrationData = migrationData; + } + @Override public void notifyChanged(Notification notification) { // do nothing diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/IMigrationParticipant.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/IMigrationParticipant.java new file mode 100644 index 0000000000..e734a7c94d --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/IMigrationParticipant.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * 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.editingcontext.migration; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.sirius.emfjson.resource.JsonResource; + +import com.google.gson.JsonObject; + +/** + * Interface of MigrationParticipant. + * + * @author mcharfadi + */ +public interface IMigrationParticipant { + String getVersion(); + + /** + * Called before a JsonResource is serialized. + * + * @param resource + * The JsonResource that is serialized + * @param jsonObject + * The root jsonObject + */ + default void preDeserialization(JsonResource resource, JsonObject jsonObject) { + + } + + /** + * Called after a JsonResource is serialized. + * + * @param resource + * The JsonResource that is serialized + * @param jsonObject + * The root jsonObject + */ + default void postSerialization(JsonResource resource, JsonObject jsonObject) { + + } + + /** + * Called during the parsing of JsonResources after loading an eObject. As such the eObject will have all his + * features set. The jsonObject is the one that was used to set the features and can be used to retrieve values + * of unknown features. + * + * @param eObject + * the eObject that have been loaded. + * @param jsonObject + * the jsonObject used to load the eObject. + */ + default void postObjectLoading(EObject eObject, JsonObject jsonObject) { + + } + + /** + * Called during the parsing of JsonResources (at loading time). If a feature value has changed since a previous + * version, use this method to return the correct expected value or null if it did not change. + * + * @param eObject + * the object containing the feature. + * @param feature + * the feature to set value. + * @param value + * the initial serialized value. + * @return the new value, or null otherwise. + */ + default Object getValue(EObject eObject, EStructuralFeature feature, Object value) { + return null; + } + + /** + * Called during the parsing of JsonResources (at loading time). It allows to + * retrieve a renamed EStructuralFeature from its old name. For example, if a + * feature 'aaa' has been renamed to 'bbb', then your MigrationParticipant + * should return the 'bbb' feature when given the 'aaa' name. + * + * @param eClass + * the given eClass. + * @param eStructuralFeatureName + * the feature name before migration. + * @return the new structural feature or null if not found. The attribute + * corresponding to given old name into given eClass. + */ + default EStructuralFeature getEStructuralFeature(EClass eClass, String eStructuralFeatureName) { + return eClass.getEStructuralFeature(eStructuralFeatureName); + } + + /** + * Called during the parsing of JsonResources (at loading time). If an + * EClassifier name has changed, then you should return the correct one. + * + * @param ePackage + * the package where looking for classifier. + * @param typeName + * the old classifier name before migration. + * @return the new classifier corresponding to the old given name into given + * ePackage or null if not found. + */ + default EClassifier getEClassifier(EPackage ePackage, String typeName) { + return ePackage.getEClassifier(typeName); + } + + /** + * Return the EPackage to use for the given namespace. + * + * @param nsURI + * the nsURI of the package we are looking for. + * @return an EPackage if some new mapping exists, null otherwise. + */ + default EPackage getPackage(String nsURI) { + return null; + } +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/MigrationExtendedMetaData.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/MigrationExtendedMetaData.java new file mode 100644 index 0000000000..d3d2e17cb8 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/MigrationExtendedMetaData.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * 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.editingcontext.migration; + +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.util.BasicExtendedMetaData; +import org.eclipse.sirius.components.emf.MigrationData; + +/** + * Specialized BasicExtendedMetaData. + * + * @author mcharfadi + */ +public class MigrationExtendedMetaData extends BasicExtendedMetaData { + private final MigrationData documentMigrationData; + + private final List migrationParticipants; + + public MigrationExtendedMetaData(List migrationParticipants, MigrationData documentMigrationData) { + this.migrationParticipants = Objects.requireNonNull(migrationParticipants); + this.documentMigrationData = Objects.requireNonNull(documentMigrationData); + } + + private boolean isCandidateVersion(IMigrationParticipant migrationParticipant) { + return migrationParticipant.getVersion().compareTo(this.documentMigrationData.migrationVersion()) > 0; + } + + @Override + public EStructuralFeature getElement(EClass eClass, String namespace, String eStructuralFeatureName) { + EStructuralFeature structuralFeature = eClass.getEStructuralFeature(eStructuralFeatureName); + var migrationParticipantsCandidate = migrationParticipants.stream() + .filter(this::isCandidateVersion) + .sorted(Comparator.comparing(IMigrationParticipant::getVersion)) + .toList(); + for (IMigrationParticipant migrationParticipant : migrationParticipantsCandidate) { + EStructuralFeature newStructuralFeature = migrationParticipant.getEStructuralFeature(eClass, eStructuralFeatureName); + if (newStructuralFeature != null) { + structuralFeature = newStructuralFeature; + } + } + return structuralFeature; + } + + @Override + public EClassifier getType(EPackage ePackage, String typeName) { + EClassifier eClassifier = ePackage.getEClassifier(typeName); + var migrationParticipantsCandidate = migrationParticipants.stream() + .filter(this::isCandidateVersion) + .sorted(Comparator.comparing(IMigrationParticipant::getVersion)) + .toList(); + for (IMigrationParticipant migrationParticipant : migrationParticipantsCandidate) { + EClassifier newEClassifier = migrationParticipant.getEClassifier(ePackage, typeName); + if (newEClassifier != null) { + eClassifier = newEClassifier; + } + } + return eClassifier; + } + + @Override + public EPackage getPackage(String nsURI) { + EPackage ePackage = super.getPackage(nsURI); + var migrationParticipantsCandidate = migrationParticipants.stream() + .filter(this::isCandidateVersion) + .sorted(Comparator.comparing(IMigrationParticipant::getVersion)) + .toList(); + for (IMigrationParticipant migrationParticipant : migrationParticipantsCandidate) { + EPackage newEPackage = migrationParticipant.getPackage(nsURI); + if (newEPackage != null) { + ePackage = newEPackage; + } + } + return ePackage; + } +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/MigrationJsonProcessor.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/MigrationJsonProcessor.java new file mode 100644 index 0000000000..872f62495e --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/MigrationJsonProcessor.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * 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.editingcontext.migration; + +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.sirius.components.emf.ResourceMetadataAdapter; +import org.eclipse.sirius.components.emf.MigrationData; +import org.eclipse.sirius.emfjson.resource.JsonResource; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +/** + * Implementation of JsonResource.IJsonResourceProcessor. + * + * @author mcharfadi + */ +public class MigrationJsonProcessor implements JsonResource.IJsonResourceProcessor { + private final MigrationData documentMigrationData; + private final List migrationParticipants; + + public MigrationJsonProcessor(List migrationParticipants, MigrationData documentMigrationData) { + this.migrationParticipants = Objects.requireNonNull(migrationParticipants); + this.documentMigrationData = Objects.requireNonNull(documentMigrationData); + } + + private boolean isCandidateVersion(IMigrationParticipant migrationParticipant) { + return migrationParticipant.getVersion().compareTo(this.documentMigrationData.migrationVersion()) > 0; + } + + @Override + public void preDeserialization(JsonResource resource, JsonObject jsonObject) { + setResourceMetadataAdapterMigrationData(resource, documentMigrationData); + var migrationParticipantsCandidates = migrationParticipants.stream() + .filter(this::isCandidateVersion) + .sorted(Comparator.comparing(IMigrationParticipant::getVersion)) + .toList(); + for (IMigrationParticipant migrationParticipant : migrationParticipantsCandidates) { + migrationParticipant.preDeserialization(resource, jsonObject); + setResourceMetadataAdapterMigrationData(resource, new MigrationData(migrationParticipant.getClass().getSimpleName(), migrationParticipant.getVersion())); + } + } + + @Override + public void postSerialization(JsonResource resource, JsonObject jsonObject) { + getOptionalResourceMetadataAdapter(resource).ifPresent(resourceMetadataAdapter -> { + if (resourceMetadataAdapter.getMigrationData() != null) { + var rootMigration = new Gson().toJsonTree(resourceMetadataAdapter.getMigrationData(), MigrationData.class); + if (rootMigration != null) { + jsonObject.add(MigrationData.JSON_OBJECT_ROOT, rootMigration); + } + } + }); + var migrationParticipantsCandidates = migrationParticipants.stream() + .filter(this::isCandidateVersion) + .sorted(Comparator.comparing(IMigrationParticipant::getVersion)) + .toList(); + for (IMigrationParticipant migrationParticipant : migrationParticipantsCandidates) { + migrationParticipant.postSerialization(resource, jsonObject); + } + } + + @Override + public void postObjectLoading(EObject eObject, JsonObject jsonObject, boolean isTopObject) { + var migrationParticipantsCandidates = migrationParticipants.stream() + .filter(this::isCandidateVersion) + .sorted(Comparator.comparing(IMigrationParticipant::getVersion)) + .toList(); + for (IMigrationParticipant migrationParticipant : migrationParticipantsCandidates) { + migrationParticipant.postObjectLoading(eObject, jsonObject); + } + } + + @Override + public Object getValue(EObject object, EStructuralFeature feature, Object value) { + Object returnValue = value; + var migrationParticipantsCandidates = migrationParticipants.stream() + .filter(this::isCandidateVersion) + .sorted(Comparator.comparing(IMigrationParticipant::getVersion)) + .toList(); + for (IMigrationParticipant migrationParticipant : migrationParticipantsCandidates) { + Object newValue = migrationParticipant.getValue(object, feature, value); + if (newValue != null) { + returnValue = newValue; + } + } + return returnValue; + } + + private Optional getOptionalResourceMetadataAdapter(JsonResource resource) { + return resource.eAdapters().stream() + .filter(ResourceMetadataAdapter.class::isInstance) + .map(ResourceMetadataAdapter.class::cast) + .findFirst(); + } + + private void setResourceMetadataAdapterMigrationData(JsonResource resource, MigrationData migrationData) { + getOptionalResourceMetadataAdapter(resource).ifPresent(resourceMetadataAdapter -> resourceMetadataAdapter.setMigrationData(migrationData)); + } + +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/participants/NodeDescriptionLabelExpressionMigrationParticipant.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/participants/NodeDescriptionLabelExpressionMigrationParticipant.java new file mode 100644 index 0000000000..08d92bbc38 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/migration/participants/NodeDescriptionLabelExpressionMigrationParticipant.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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.editingcontext.migration.participants; + +import java.util.Optional; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.sirius.components.view.diagram.DiagramPackage; +import org.eclipse.sirius.components.view.diagram.ImageNodeStyleDescription; +import org.eclipse.sirius.components.view.diagram.InsideLabelDescription; +import org.eclipse.sirius.components.view.diagram.NodeDescription; +import org.eclipse.sirius.components.view.diagram.OutsideLabelDescription; +import org.eclipse.sirius.components.view.diagram.RectangularNodeStyleDescription; +import org.eclipse.sirius.web.application.editingcontext.migration.IMigrationParticipant; + +import com.google.gson.JsonObject; +import org.springframework.stereotype.Service; + +/** + * MigrationParticipant that update the LabelExpression of NodeDescription with + * InsideLabelDescription or OutsideLabelDescription. + * + * @author mcharfadi + */ +@Service +public class NodeDescriptionLabelExpressionMigrationParticipant implements IMigrationParticipant { + + private static final String PARTICIPANT_VERSION = "2024.4.0"; + + @Override + public String getVersion() { + return PARTICIPANT_VERSION; + } + + @Override + public void postObjectLoading(EObject eObject, JsonObject jsonObject) { + if (eObject instanceof NodeDescription nodeDescription) { + var optionalNodeDescriptionData = Optional.ofNullable(jsonObject.getAsJsonObject("data")); + optionalNodeDescriptionData.ifPresent(nodeDescriptionData -> { + var optionalLabelExpression = Optional.ofNullable(nodeDescriptionData.get("labelExpression")); + optionalLabelExpression.ifPresent(labelExpression -> { + if (nodeDescription.getStyle() instanceof RectangularNodeStyleDescription) { + InsideLabelDescription insideLabelDescription = DiagramPackage.eINSTANCE.getDiagramFactory().createInsideLabelDescription(); + insideLabelDescription.setLabelExpression(labelExpression.getAsString()); + nodeDescription.setInsideLabel(insideLabelDescription); + } + if (nodeDescription.getStyle() instanceof ImageNodeStyleDescription) { + OutsideLabelDescription outsideLabelDescription = DiagramPackage.eINSTANCE.getDiagramFactory().createOutsideLabelDescription(); + outsideLabelDescription.setLabelExpression(labelExpression.getAsString()); + nodeDescription.getOutsideLabels().add(outsideLabelDescription); + } + }); + }); + } + } +} \ No newline at end of file diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/ResourceLoader.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/ResourceLoader.java index b6fca1918e..96a0f29eb9 100644 --- a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/ResourceLoader.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/ResourceLoader.java @@ -15,15 +15,26 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; import java.util.Optional; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.sirius.components.emf.ResourceMetadataAdapter; import org.eclipse.sirius.components.emf.services.JSONResourceFactory; +import org.eclipse.sirius.components.emf.MigrationData; +import org.eclipse.sirius.emfjson.resource.JsonResource; +import org.eclipse.sirius.web.application.editingcontext.migration.IMigrationParticipant; +import org.eclipse.sirius.web.application.editingcontext.migration.MigrationExtendedMetaData; +import org.eclipse.sirius.web.application.editingcontext.migration.MigrationJsonProcessor; import org.eclipse.sirius.web.application.editingcontext.services.api.IResourceLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import org.springframework.stereotype.Service; /** @@ -34,18 +45,34 @@ @Service public class ResourceLoader implements IResourceLoader { + private static final MigrationData EMPTY_MIGRATION_DATA = new MigrationData("none", "0"); + private final Logger logger = LoggerFactory.getLogger(ResourceLoader.class); + private final List migrationParticipants; + + public ResourceLoader(List migrationParticipants) { + this.migrationParticipants = migrationParticipants; + } + @Override public Optional toResource(ResourceSet resourceSet, String id, String name, String content) { Optional optionalResource = Optional.empty(); + var migrationData = getMigrationDataFromDocumentContent(content).orElse(EMPTY_MIGRATION_DATA); + var migrationExtendedMetaData = new MigrationExtendedMetaData(this.migrationParticipants, migrationData); + var migrationJsonProcessor = new MigrationJsonProcessor(this.migrationParticipants, migrationData); + + HashMap options = new HashMap<>(); + options.put(JsonResource.OPTION_EXTENDED_META_DATA, migrationExtendedMetaData); + options.put(JsonResource.OPTION_JSON_RESSOURCE_PROCESSOR, migrationJsonProcessor); + var resource = new JSONResourceFactory().createResourceFromPath(id); + try (var inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) { resourceSet.getResources().add(resource); - resource.load(inputStream, null); - resource.eAdapters().add(new ResourceMetadataAdapter(name)); + resource.load(inputStream, options); optionalResource = Optional.of(resource); } catch (IOException | IllegalArgumentException exception) { @@ -55,4 +82,10 @@ public Optional toResource(ResourceSet resourceSet, String id, String return optionalResource; } + + public Optional getMigrationDataFromDocumentContent(String content) { + JsonObject jsonObject = JsonParser.parseString(content).getAsJsonObject(); + return Optional.ofNullable(jsonObject.getAsJsonObject(MigrationData.JSON_OBJECT_ROOT)) + .map(migrationRootElement -> new Gson().fromJson(migrationRootElement, MigrationData.class)).stream().findFirst(); + } } diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/ResourceToDocumentService.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/ResourceToDocumentService.java index 6cb3f0546c..949d76f276 100644 --- a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/ResourceToDocumentService.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/services/ResourceToDocumentService.java @@ -15,18 +15,24 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Optional; import java.util.UUID; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.sirius.components.emf.ResourceMetadataAdapter; import org.eclipse.sirius.components.emf.services.EObjectIDManager; +import org.eclipse.sirius.components.emf.MigrationData; import org.eclipse.sirius.emfjson.resource.JsonResource; import org.eclipse.sirius.web.application.UUIDParser; +import org.eclipse.sirius.web.application.editingcontext.migration.IMigrationParticipant; +import org.eclipse.sirius.web.application.editingcontext.migration.MigrationExtendedMetaData; +import org.eclipse.sirius.web.application.editingcontext.migration.MigrationJsonProcessor; import org.eclipse.sirius.web.application.editingcontext.services.api.IResourceToDocumentService; import org.eclipse.sirius.web.domain.boundedcontexts.semanticdata.Document; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import org.springframework.stereotype.Service; /** @@ -37,16 +43,29 @@ @Service public class ResourceToDocumentService implements IResourceToDocumentService { + private static final MigrationData EMPTY_MIGRATION_DATA = new MigrationData("none", "0"); + private final Logger logger = LoggerFactory.getLogger(ResourceToDocumentService.class); + private final List migrationParticipants; + + public ResourceToDocumentService(List migrationParticipants) { + this.migrationParticipants = migrationParticipants; + } + @Override public Optional toDocument(Resource resource) { + var migrationData = getOptionalResourceMetadataAdapter(resource).map(ResourceMetadataAdapter::getMigrationData).orElse(EMPTY_MIGRATION_DATA); var serializationListener = new JsonResourceSerializationListener(); + var migrationJsonProcessor = new MigrationJsonProcessor(this.migrationParticipants, migrationData); + var migrationExtendedMetaData = new MigrationExtendedMetaData(this.migrationParticipants, migrationData); HashMap options = new HashMap<>(); options.put(JsonResource.OPTION_ID_MANAGER, new EObjectIDManager()); options.put(JsonResource.OPTION_SCHEMA_LOCATION, true); options.put(JsonResource.OPTION_SERIALIZATION_LISTENER, serializationListener); + options.put(JsonResource.OPTION_JSON_RESSOURCE_PROCESSOR, migrationJsonProcessor); + options.put(JsonResource.OPTION_EXTENDED_META_DATA, migrationExtendedMetaData); Optional optionalDocumentData = Optional.empty(); try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { @@ -81,4 +100,11 @@ public Optional toDocument(Resource resource) { } return optionalDocumentData; } + + public Optional getOptionalResourceMetadataAdapter(Resource resource) { + return resource.eAdapters().stream() + .filter(ResourceMetadataAdapter.class::isInstance) + .map(ResourceMetadataAdapter.class::cast) + .findFirst(); + } } diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/data/MigrationIdentifiers.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/data/MigrationIdentifiers.java new file mode 100644 index 0000000000..fa57c7deea --- /dev/null +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/data/MigrationIdentifiers.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * 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.data; + +import java.util.UUID; + +/** + * Used to store some test identifiers related to the migration projects. + * + * @author mcharfadi + */ +public final class MigrationIdentifiers { + + public static final UUID MIGRATION_STUDIO = UUID.fromString("a3b86086-23f5-41cb-97b9-5ac7234a98af"); + + public static final String MIGRATION_STUDIO_DIAGRAM = "NodeDescription#labelExpression migration"; + + private MigrationIdentifiers() { + // Prevent instantiation + } +} diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/migration/MigrationParticipantTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/migration/MigrationParticipantTests.java new file mode 100644 index 0000000000..5c97ab9e90 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/services/migration/MigrationParticipantTests.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * 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.services.migration; + +import org.eclipse.sirius.components.core.api.IEditingContextSearchService; +import org.eclipse.sirius.components.view.diagram.DiagramDescription; +import org.eclipse.sirius.components.view.diagram.ImageNodeStyleDescription; +import org.eclipse.sirius.components.view.diagram.RectangularNodeStyleDescription; +import org.eclipse.sirius.web.AbstractIntegrationTests; +import org.eclipse.sirius.web.application.editingcontext.EditingContext; +import org.eclipse.sirius.web.data.MigrationIdentifiers; + +import static org.assertj.core.api.Assertions.assertThat; +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.transaction.annotation.Transactional; + +/** + * Integration tests of NodeDescriptionLabelExpressionMigrationParticipant. + * + * @author mcharfadi + */ +@Transactional +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class MigrationParticipantTests extends AbstractIntegrationTests { + + @Autowired + private IEditingContextSearchService editingContextSearchService; + + @Test + @DisplayName("Given a project with an old model, NodeDescriptionLabelExpressionMigrationParticipant migrates the model correctly") + @Sql(scripts = {"/scripts/migration.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 givenAnOldModelMigrationParticipantCanBeContributedToUpdateTheModel() { + var optionalEditingContext = this.editingContextSearchService.findById(MigrationIdentifiers.MIGRATION_STUDIO.toString()); + assertThat(optionalEditingContext).isPresent(); + var editingContext = optionalEditingContext.get(); + if (editingContext instanceof EditingContext siriusWebEditingContext) { + var optionalDiagramDescription = siriusWebEditingContext.getViews().stream().flatMap(view -> view.getDescriptions().stream()).filter(representationDescription -> representationDescription.getName().equals(MigrationIdentifiers.MIGRATION_STUDIO_DIAGRAM)).findFirst(); + assertThat(optionalDiagramDescription).isPresent(); + assertThat(optionalDiagramDescription.get()).isInstanceOf(DiagramDescription.class); + optionalDiagramDescription.ifPresent(representationDescription -> { + if(representationDescription instanceof DiagramDescription diagramDescription) { + assertThat(diagramDescription.getNodeDescriptions()).hasSize(2); + diagramDescription.getNodeDescriptions().forEach(nodeDescription -> { + if (nodeDescription.getStyle() instanceof RectangularNodeStyleDescription) { + assertThat(nodeDescription.getInsideLabel().getLabelExpression()).isEqualTo("aql:'NodeWithoutImage'"); + } + if (nodeDescription.getStyle() instanceof ImageNodeStyleDescription) { + assertThat(nodeDescription.getOutsideLabels().get(0).getLabelExpression()).isEqualTo("aql:'NodeWithImage'"); + } + }); + } + }); + } + } + +} diff --git a/packages/sirius-web/backend/sirius-web/src/test/resources/scripts/migration.sql b/packages/sirius-web/backend/sirius-web/src/test/resources/scripts/migration.sql new file mode 100644 index 0000000000..cc462e8342 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web/src/test/resources/scripts/migration.sql @@ -0,0 +1,119 @@ +-- Sample migration project +INSERT INTO project ( + id, + name, + created_on, + last_modified_on +) VALUES ( + 'a3b86086-23f5-41cb-97b9-5ac7234a98af', + 'Migration Studio', + '2024-01-01 9:42:0.000', + '2024-01-02 9:42:0.000' +); +INSERT INTO nature ( + project_id, + name +) VALUES ( + 'a3b86086-23f5-41cb-97b9-5ac7234a98af', + 'siriusComponents://nature?kind=studio' +); +INSERT INTO semantic_data ( + id, + project_id, + created_on, + last_modified_on +) VALUES ( + '89d67892-0cc9-4ca4-b30e-28688470c0d4', + 'a3b86086-23f5-41cb-97b9-5ac7234a98af', + '2024-01-01 9:42:0.000', + '2024-01-02 9:42:0.000' +); +INSERT INTO semantic_data_domain ( + semantic_data_id, + uri +) VALUES ( + '89d67892-0cc9-4ca4-b30e-28688470c0d4', + 'http://www.eclipse.org/sirius-web/view' +); +INSERT INTO semantic_data_domain ( + semantic_data_id, + uri +) VALUES ( + '89d67892-0cc9-4ca4-b30e-28688470c0d4', + 'http://www.eclipse.org/sirius-web/diagram' +); +INSERT INTO document ( + id, + semantic_data_id, + name, + content, + created_on, + last_modified_on +) VALUES ( + '1d8aac3e-5fe7-4787-b0fc-1f8eb491cd5e', + '89d67892-0cc9-4ca4-b30e-28688470c0d4', + 'NodeDescription#labelExpression migration', + '{ + "json": { "version": "1.0", "encoding": "utf-8" }, + "ns": { + "diagram": "http://www.eclipse.org/sirius-web/diagram", + "view": "http://www.eclipse.org/sirius-web/view" + }, + "content": [ + { + "id": "9674f8f7-ff1a-4061-bb32-a4a235a9c2ca", + "eClass": "view:View", + "data": { + "descriptions": [ + { + "id": "22fb1f4d-109d-4e73-bff0-f7cd96fb5fbb", + "eClass": "diagram:DiagramDescription", + "data": { + "name": "NodeDescription#labelExpression migration", + "domainType": "flow::System", + "nodeDescriptions": [ + { + "id": "6949ddfe-f480-473b-bc8a-6f2bdde07e4d", + "eClass": "diagram:NodeDescription", + "data": { + "name": "NodeWithoutImage migration", + "domainType": "flow::CompositeProcessor", + "labelExpression": "aql:''NodeWithoutImage''", + "childrenLayoutStrategy": { + "id": "20651d93-2ee5-41cb-b2bd-1e75958c73cf", + "eClass": "diagram:FreeFormLayoutStrategyDescription" + }, + "style": { + "id": "92fe9d3f-2c5b-41ab-81ea-8482c8cd57b9", + "eClass": "diagram:RectangularNodeStyleDescription" + } + } + }, + { + "id": "88f95390-ac15-4381-ae82-7b23a2017bd4", + "eClass": "diagram:NodeDescription", + "data": { + "name": "NodeWithImage migration", + "domainType": "flow::CompositeProcessor", + "labelExpression": "aql:''NodeWithImage''", + "childrenLayoutStrategy": { + "id": "db92e810-5d51-4d0c-a6cf-6a1e00cea6d9", + "eClass": "diagram:FreeFormLayoutStrategyDescription" + }, + "style": { + "id": "fa35806a-bdd0-4ba7-81bf-8cfba8f8fef5", + "eClass": "diagram:ImageNodeStyleDescription" + } + } + } + ] + } + } + ] + } + } + ] + }', + '2024-01-01 9:42:0.000', + '2024-01-02 9:42:0.000' +); \ No newline at end of file