Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[22] Support Metamodel migration, POJO datatype and feature serialization ordering #21

Conversation

vrichard12
Copy link
Contributor

@vrichard12 vrichard12 commented Nov 6, 2023

Addresses the following subjects:

  • Handling the renaming of EAttribute
    Renaming of EAttribute is handled by the ExtendedMetaData.getElement() API. Examples of such a migration is provided in the org.eclipse.sirius.emfjson.tests.internal.unit.load.ExtendedMetaDataAttributesLoadTests test class.

  • Handling a type change or a value change of an EAttribute
    Changing the type or the value of an EAttribute is handled with the JsonHelper.setValue() API. Exemple of such migrations are provided in the org.eclipse.sirius.emfjson.tests.internal.unit.load.JsonHelperDataLoadTests test class.

  • Support for Serialization / deserialization of POJO EDataType
    Custom data types with instance type set to a POJO class are serialized to / deserialized from json. The corresponding tests are :
    org.eclipse.sirius.emfjson.tests.internal.unit.load.DataTypeLoadTests.testLoadSingleValueAttributePojoDataType()
    org.eclipse.sirius.emfjson.tests.internal.unit.load.DataTypeLoadTests.testLoadMultiValuedAttributePojoDataType()
    org.eclipse.sirius.emfjson.tests.internal.unit.save.DataTypeSaveTests.testSaveSingleValueAttributePojoDataType()
    org.eclipse.sirius.emfjson.tests.internal.unit.save.DataTypeSaveTests.testSaveMultiValuedAttributePojoDataType()

  • A comparator option on the JsonResource to determine the order in which features are serialized
    org.eclipse.sirius.emfjson.tests.internal.unit.save.SerializeOptionsTests.testSerializationWithFeatureOrderComparator()

@vrichard12
Copy link
Contributor Author

fixes #22

@vrichard12 vrichard12 changed the title Support Metamodel migration, POJO datatype and feature serialization ordering Support Metamodel migration, POJO datatype and feature serialization ordering (fixes #22) Nov 6, 2023
@gcoutable gcoutable changed the title Support Metamodel migration, POJO datatype and feature serialization ordering (fixes #22) [22] Support Metamodel migration, POJO datatype and feature serialization ordering Nov 6, 2023
@gcoutable gcoutable added the enhancement New feature or request label Nov 6, 2023
@@ -19,5 +19,5 @@ Import-Package: javax.xml.bind;version="0.0.0",
org.eclipse.emf.ecore.resource;version="0.0.0",
org.eclipse.emf.ecore.resource.impl;version="0.0.0",
org.eclipse.emf.ecore.util;version="0.0.0"
Require-Bundle: javax.xml.bind
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you removing and then adding back java.xml.bind and why remove its version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't remove it, I added it. It was missing and it was blocking the build.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you are wrong here, you literally removed it in the first commit of this pull request:

Capture d’écran 2024-01-10 à 17 04 57

Before adding it back as I said but without the version number:

Capture d’écran 2024-01-10 à 17 05 49

So I'm trying to understand both changes, I suspect that the first one could be removed and the second one too.

Comment on lines 920 to 965
private static final List<EDataType> ECORE_PACKAGE_DATA_TYPES = Arrays.asList(EcorePackage.eINSTANCE.getEString(), EcorePackage.eINSTANCE.getEBoolean(), EcorePackage.eINSTANCE.getEBooleanObject(),
EcorePackage.eINSTANCE.getEInt(), EcorePackage.eINSTANCE.getEIntegerObject(), EcorePackage.eINSTANCE.getEBigDecimal(), EcorePackage.eINSTANCE.getEBigInteger(),
EcorePackage.eINSTANCE.getEByte(), EcorePackage.eINSTANCE.getEByteArray(), EcorePackage.eINSTANCE.getEByteObject(), EcorePackage.eINSTANCE.getEChar(),
EcorePackage.eINSTANCE.getECharacterObject(), EcorePackage.eINSTANCE.getEDate(), EcorePackage.eINSTANCE.getEDouble(), EcorePackage.eINSTANCE.getEDoubleObject(),
EcorePackage.eINSTANCE.getEFloat(), EcorePackage.eINSTANCE.getEFloatObject(), EcorePackage.eINSTANCE.getELong(), EcorePackage.eINSTANCE.getELongObject(),
EcorePackage.eINSTANCE.getEShort(), EcorePackage.eINSTANCE.getEShortObject());

/**
* Tests if the given {@link EDataType} is a POJO.
*
* @param dataType
* A {@link EDataType} to test
* @return true if the given {@link EDataType} is not one of the {@link EcorePackage} DataTypes nor an
* {@link EEnum}.
*/
private boolean isPojo(EDataType dataType) {
return !ECORE_PACKAGE_DATA_TYPES.contains(dataType) && //
dataType.eClass() != EcorePackage.eINSTANCE.getEEnum() && //
dataType.getInstanceClass() != null;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we consider that if something is not from Ecore then we should not leverage the method EcoreUtil.createFromString? Is there anything like that in XMIResource? If not, I don't understand the need for an ad hoc serialization system in our code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EcoreUtil.createFromString does not handle json format. I don't see your point.
What would anything be like in XMIResource?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In XMI resource, you would have to implement the expected methods from EMF and you would not return XML data structure this way. But if done properly, your meta-model could work for every kind of resources, XMI, Json, binary, etc

Copy link
Member

@sbegaudeau sbegaudeau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request mixes commits which look good from afar (I have not tested nor really reviewed them) and a couple of commits which are a bit odd for me at the moment. Such a big PR mixing various concerns is quite slow to integrate.

For the content that seems odd to me, I don't see why we would need a custom serialization mechanism on top of the EMF one nor why we should change the current behavior for datatypes.

@vrichard12
Copy link
Contributor Author

As I said in the associated issue #22, before this evolution, the POJO serialization felt back to a toString() call, producing this kind of string: org.obeonetwork.some.package.SomeType@147c1776. What we want is to be able to handle a json object structure in order to be fully compliant with the json format.
We need a custom serialization mechanism on top of the EMF one to handle the Json format. That's the purpose of JsonResourceImpl. All we do here is add the support for a corner case that wasn't handled: custom datatypes with an instance type name referring to a POJO.

@vrichard12 vrichard12 force-pushed the migration-pojodatatype-featureordering branch from 4505bc8 to 22f04b9 Compare November 22, 2023 16:18
@lfasani lfasani assigned lfasani and vrichard12 and unassigned lfasani Dec 13, 2023
@sbegaudeau sbegaudeau force-pushed the migration-pojodatatype-featureordering branch from 22f04b9 to 63ed710 Compare January 11, 2024 06:50
@sbegaudeau
Copy link
Member

I've merged 90% of the commits of this pull request as part of the pr #31 and performed the release of EMFJson 2.3.6. This way, we can continue our discussion regarding the last commit since I still have some issues with the code.

@vrichard12 vrichard12 force-pushed the migration-pojodatatype-featureordering branch 4 times, most recently from 9ee6461 to 17dc1c4 Compare January 29, 2024 16:16
@vrichard12
Copy link
Contributor Author

Hi Stéphane,

To get everyone in agreement, I've set up an option to specify which POJO should be considered by Json serialization/deserialization (OPTION_IS_EDATATYPE_SERIALIZABLE_IN_JSON_TESTER).

You'll notice that I've also avoided calling super.getElement() in the BasicExtendedMetaData subclasses to avoid falling into the XMI-specific processing that has been implemented in BasicExtendedMetaData.

Finally, I had to correct a behavior in GsonEObjectSerializer.serializeEDataType() in the case of a single-valued attribute. When the stringValue was null, the code created a JsonPrimitive(""). This poses a problem when serializing POJO type attributes, where it cannot be serialized as the empty string. When the default value of an attribute is null, and the value of the attribute in the model to be serialized is also null, nothing should be serialized.

@@ -425,6 +428,12 @@ interface IEObjectHandler {
*/
Object OPTION_SAVE_FEATURES_ORDER_COMPARATOR = "OPTION_SAVE_FEATURES_ORDER_COMPARATOR"; //$NON-NLS-1$

/**
* Specify a {@link Function} returning {@link Boolean#TRUE} if a given {@link EDataType} should be serialized
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good remark from Gionatta. Prefer a Predicate that is designed for this need.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely yes! Thank you for the remark. I fixed that.

* @param eDataType
* @return true if the given {@link EDataType} should be deserialized from a Json tree.
*/
private boolean isEDataTypeSerializableInJson(EDataType eDataType) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isEDataTypeSerializableInJson -> isSerializeEDataTypeInJson

if (this.isEDataTypeSerializableInJson(eAttribute.getEAttributeType())) {
jsonElement = new Gson().toJsonTree(value);
} else {
if (stringValue == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer checking null first like line 1046. It avoid letting the given Predicate manage this case (if any)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you're right, the code will be cleaner like that. I fix that right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 1079 to 1102
private boolean isEDataTypeSerializableInJson(EDataType eDataType) {
@SuppressWarnings("unchecked")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isEDataTypeSerializableInJson -> isSerializeEDataTypeInJson

Copy link
Contributor Author

@vrichard12 vrichard12 Jan 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming "isSerializeEDataTypeInJson" suggests that the given EDataType is serializable and that, based on this assumption, tests whether the "Json" serialization is a valid target for serialization. I find this naming incorrect. I prefer to keep "isEDataTypeSerializableInJson", which says exactly what the test does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually you're right on one point. The test should not say "can" but "should". I propose to rename it "shouldEDataTypeBeSerializedInJson" which reflect the real intent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed the code accordingly.

@vrichard12 vrichard12 force-pushed the migration-pojodatatype-featureordering branch 2 times, most recently from 4a427b6 to 4eb40c1 Compare January 31, 2024 09:37
@lredor lredor requested a review from sbegaudeau March 5, 2024 14:54
Comment on lines 1057 to 1086
String stringValue = eFactoryInstance.convertToString(eAttribute.getEAttributeType(), value);

if (stringValue == null) {
stringValue = ""; //$NON-NLS-1$
jsonElement = JsonNull.INSTANCE;
} else if (this.shouldEDataTypeBeSerializedInJson(eAttribute.getEAttributeType())) {
jsonElement = new Gson().toJsonTree(value);
} else {
jsonElement = new JsonPrimitive(stringValue);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that shouldEDataTypeBeSerializedInJson() should be called first, like:

 if (this.shouldEDataTypeBeSerializedInJson(eAttribute.getEAttributeType())) {
      jsonElement = new Gson().toJsonTree(value);
  } else {
      String stringValue = eFactoryInstance.convertToString(eAttribute.getEAttributeType(), value);
      if (stringValue == null) {
          jsonElement = JsonNull.INSTANCE;
      } else {
          jsonElement = new JsonPrimitive(stringValue);
      }
  }

It's the same beahviour of the eAttribute.isMany() case above.

@sbegaudeau sbegaudeau force-pushed the migration-pojodatatype-featureordering branch from 4eb40c1 to 36e85fa Compare May 24, 2024 13:05
@sbegaudeau sbegaudeau linked an issue May 24, 2024 that may be closed by this pull request
@sbegaudeau sbegaudeau added this to the 2024.7.0 milestone May 24, 2024
Signed-off-by: Stéphane Bégaudeau <stephane.begaudeau@obeo.fr>
@sbegaudeau sbegaudeau merged commit e13d502 into eclipse-sirius:master May 24, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
5 participants