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

Support for Extrinsic XMI IDs #62

Open
kinahawi opened this issue Feb 21, 2021 · 12 comments
Open

Support for Extrinsic XMI IDs #62

kinahawi opened this issue Feb 21, 2021 · 12 comments

Comments

@kinahawi
Copy link

When resolving an XMI model that uses XMI:ID attributes, NMF successfully resolves references using those IDs. When saving a model, NMF does not use the XMI:ID attribute but rather uses the URL path to the element instead. This causes a problem in round tripping data with other tools that depend on the XMI:ID. Is there a way to set the desired behavior? The goal is to utilize "Extrinsic IDs" as outlined in the EMF modeling guide.

@georghinkel
Copy link
Contributor

Hi,

the short answer is that NMF always tries to use index-based identifiers until either this is not possible (because the collection underneath is not ordered) or elements explicitly declare identifiers. In the first case, NMF uses XMI IDs, but I understand that you would probably not want to use unordered collections all over the place just to force NMF to use XMI IDs.

I guess this can be easily implemented as a flag in the serializer. Give me a couple of days.

Best,

Georg

@kinahawi
Copy link
Author

kinahawi commented Feb 22, 2021 via email

@georghinkel
Copy link
Contributor

c1f29bf should add this functionality, integrated into the model repositories. There is also a test that checks that the ids are on all model elements. The changes should be on Nuget in about 10min. Please reopen, if your problem persists.

@kinahawi
Copy link
Author

kinahawi commented Feb 23, 2021 via email

@georghinkel
Copy link
Contributor

Yeah, I am having problems that Nuget somehow does not accept my API key. The old one was expired but I actually tried several attempts now to assign a new one, unfortunately with little success so far.

@kinahawi
Copy link
Author

Georg,
I tried the changes from locally built nuget packages and I see that you have added IDs to each element when it is serialized, but I think I was not clear on what was needed. Each element does have to have an ID, but if is not a newly created element, it should retain the ID it had when it was first deserialized. I see this information is present in the idStore property of the model so perhaps that can be checked before assigning a new ID? Of course the dictionary would have to be "reversed" to look up by object.

Also, I see many cases where a reference from one element to another uses a name rather than the XMI:ID. Non-containment references need to use the XMI:ID of the element that is being referenced. I've only started trying to step through the code but am noticing that NMF makes the "name" property an ID by default. Is there a way to turn that off in the generator?

Most often a UUID is generated when a new ID is required. Is it possible, for cases when an ID needs to be generated, that a user supplied ID generator is used? That way it can be adapted to whatever might be required.

My plan is to continue to step through the serialization code to understand how it works and perhaps offer up some changes if I figure it out on my own. Its somewhat complex and I do worry that I'll break something along the way.

Thanks for the support, and again my complements on creating such useful technology!

@georghinkel
Copy link
Contributor

The serialization code is actually the oldest part of NMF. I wrote large parts of it in my first and second term in university and then extended the code when necessary. This is also why the base is an XmlSerializer that is actually not really used. The entire serialization started with an idea to implement an XML serializer that was able to cope with circular references and only afterwards I tried to implement XMI as a special case...

I guess the easiest option here would be to put a transient extension on the model elements to store the id, much like this is done for types to store their .NET type mapping. Afterwards, you could use this extension to obtain the id in serialization. That way, you have the full flexibility how you generate the id, if not present.

@kinahawi
Copy link
Author

I'm not sure I understand what you are suggesting and I'm afraid my knowledge of C# isn't nearly as deep as yours. Are you suggesting the use of a Service provider with dependency injection for ID generation? Or type extension where I define an extension method? If the latter, would that extension go on the ModelElement class? Would that require a Serializer and Deserializer that is cognizant of the extension?

I'm certainly willing to try to work on an implementation, but a little more detailed advice would be extremely helpful to get me started.

Best regards.

@georghinkel
Copy link
Contributor

Extensions are essentially the implementation of stereotypes in NMeta. You can see a MappedType extension in NMeta itself. It is used to attach a .NET type to an NMeta type. The concept is a bit like component-based concept that you see in game development (not the components used in software architectures!). The deserializer could just put the extension on the model elements to store their id regardless of the type of element. I mean, you can't really derive from the concrete class of the elements directly because you would have to do that for each class and even if so, the deserializer would not use your derived classes. Extensions on the other hand are something you can just attach to any model element (except that extensions can be constrained to certain types of model elements). The serializer could then read the id property of the extension if that exists and roll a new id otherwise.

@kinahawi
Copy link
Author

Are you meaning that IDs be added to each element by storing them in the IModelElement Extensions member during deserialization? I looked into doing that by modifying the Set operation on the XmiArtificialIDAttribute and having it not only add the ID to the serialization context, but adding itself to the Extensions on the IModelElement being deserialized. The problem I encounter is that the Serialization project has no knowledge of ModelElements and therefore no access to their extensions. I can't add a reference to the Models project because that creates cycles in the project dependencies.

I also looked at a more brute force approach of adding code to the ExplicitIdSerializer to add each Id to the extensions of each element at the conclusion of Deserialization and the doing something similar at the start of serializing, but the Deserialize/Serialize members are not virtual and I wasn't sure you would want to change that.

Sorry if I misunderstand your suggestions. Thanks again for any your assistance.

@georghinkel
Copy link
Contributor

As said, the serialization code is the oldest in NMF. I eventually introduced extension points just as I needed them because it would have been too much effort to redesign the serialization component and the code had too much merits that I did not want to give up.

The easy answer is that the property serialization that is used for the id is a virtual property, so you can just inherit from the XmiArtificialIdProperty and assign that property in a serializer inherited from ModelSerializer or change the ExplicitIdSerializer directly.

BTW, the build currently does not work because somehow AppVeyor fails to push the packages to Nuget.org

@kinahawi
Copy link
Author

kinahawi commented Mar 2, 2021

It sounds so easy when you describe it like that!

Regarding overriding XmlArtificialIdAttribute I'm not sure how to get existing code to use this. The deserializer, which is initialized during construction of the repository, uses XmiArtificialId.Instance when loading in all of the meta data. I don't see an easy way to override this behavior since loading of the repositories is done in the construction of the MetaRepository and it is not something I can call externally. I can create an instance of the ExplicitIdSerializer using the one created in the repository as parent, but the XmiArtificialIds remain for each type and are employed during deserialization. I can use a class that inherits from XmiArtificialId during serialization because a newly created serializer seems to build the property info as needed.

I created an ExplicitIdExtension element to add to model extensions per your original suggestion. The only way I could figure out to add it was to override InitializeAttributeProperties to add the extension whenever is came across a ModelIdAttribute property. This worked in that it added the extension to each element, but it caused other problems. First, the extension is now a child element of each model element, so my tree control that allows the user to browse the model sees these extensions. Second, and even more of a problem, the serializer tries to serialize it and end up with duplicate XMI. I've tried setting the XmlAttributeAttribute = false on the Id property of the extension but this seems to have no effect. I've been unable to find any ways to make the extension "transient" as you suggested in your earlier e-mail.

After spending a good number of hours trying to get this to work in a manner consistent with your existing design, I ended up adding dictionaries to the ExplicitIdSerializer that are populated with IDs during deserialization, and then used to restore IDs during serialization with IDs being added for newly created elements. Of course, if one were to use a different serializer than the one used during deserialization, none of the original IDs would be available and new ones would be created for every element. This doesn't seem consistent with what you've been trying to accomplish in the design, but I'm happy to post it if you are interested. Let me know if you have any additional suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants