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

Add a YAML file provider for semantic tags #3659

Merged
merged 19 commits into from
Dec 10, 2023

Conversation

lolodomo
Copy link
Contributor

@lolodomo lolodomo commented Jun 17, 2023

Files in folder conf/tags are loaded by this provider.

Related to #3619

Here is an example of file:

#
# Configuration file containing a list of custom semantic tags
#

version: 1

# For each semantic tag:
#  - uid and label are required
#  - description and synonyms are optional

tags:

  # Example of a new equipment
  - uid: Equipment_Curtain
    label: Curtain

  # Example of a new location
  - uid: Location_Indoor_Room_HomeCinemaRoom
    label: Home Cinema Room
    description: The room containing my home cinema system.
    synonyms:
      - Home theater room
      - TV room
      - Movie room

Signed-off-by: Laurent Garnier [email protected]

@lolodomo lolodomo requested a review from a team as a code owner June 17, 2023 08:10
@lolodomo lolodomo marked this pull request as draft June 17, 2023 08:10
@J-N-K
Copy link
Member

J-N-K commented Jun 17, 2023

We should probably go one step back and think about the whole design of the YAML files. I'll create a RFC for that this afternoon as a GitHUB discussion.

@lolodomo
Copy link
Contributor Author

I'll create a RFC for that this afternoon as a GitHUB discussion.

I don't find it, did you create it?

@J-N-K
Copy link
Member

J-N-K commented Jun 20, 2023

Sorry, I was a busy with real life issues. I'll try to write that this evening. With the concept I have in mind, the provider would not even need to handle anything except providing a DTO and registering itself with something similar to the ModelRepository.

@lolodomo
Copy link
Contributor Author

Build failed because new dependencies with YAML libraries need to be added in each integration tests.

@lolodomo
Copy link
Contributor Author

@J-N-K : I have now implemented your suggestion in #3666 . I would like your feedback.
I add comments on difficulties I encounter(ed) or changes compared to your initial suggestion.

Comment on lines 28 to 32
void addedModel(String modelName, List<? extends YamlElement> elements);

void updatedModel(String modelName, List<? extends YamlElement> elements);

void removedModel(String modelName, List<? extends YamlElement> elements);
Copy link
Contributor Author

@lolodomo lolodomo Jun 28, 2023

Choose a reason for hiding this comment

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

It is the biggest problem I encountered.
@J-N-K : your suggestion was to use List<T> as parameter type. Makes sense. Unfortunately, in this case, I am not able to call these methods in processWatchEvent due to an error in Eclipse. Type argument is wrong and I don't know how to cast to the type expected by the compiler.
Advice would be appreciated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@J-N-K : WDYT about that ?

@lolodomo lolodomo force-pushed the YAMLfile_semantic_tag_provider branch from 18dd2c1 to 5a3b3ac Compare June 28, 2023 16:48
Files in folder conf/tags are loaded by this provider.

Related to openhab#3619

Signed-off-by: Laurent Garnier <[email protected]>
@lolodomo lolodomo force-pushed the YAMLfile_semantic_tag_provider branch from 5a3b3ac to a6d93c1 Compare June 28, 2023 16:57
Signed-off-by: Laurent Garnier <[email protected]>
@lolodomo
Copy link
Contributor Author

A help is welcome to tell me what to add in itest.bndrun to make integration tests compile. Stuff related to YAML libraries has to be added.

@J-N-K
Copy link
Member

J-N-K commented Jul 3, 2023

The watch service listener code will not work this way. I'll create a PR to your repo that makes it work.

@lolodomo
Copy link
Contributor Author

lolodomo commented Jul 7, 2023

@J-N-K : any chance to have your help and review?

@J-N-K
Copy link
Member

J-N-K commented Jul 8, 2023

See my commit, I didn't test it, but it should work (at least the structure should now be ok).

@lolodomo
Copy link
Contributor Author

lolodomo commented Jul 9, 2023

See my commit, I didn't test it, but it should work (at least the structure should now be ok).

Thank you @J-N-K .
I am looking at your changes.
Having a map by model and not by file may be a good idea but the current code can't work as it is a mix between both.
When a file is deleted, we need to retrieve easily the removed objects. That was the idea with the map per file.
I will probably revert that or create a map of map.

@J-N-K
Copy link
Member

J-N-K commented Jul 9, 2023

You can also do it per-file, that doesn't matter. The important thing is that you need to correctly resolve the path that comes from the watch service.

@lolodomo
Copy link
Contributor Author

lolodomo commented Jul 9, 2023

You can also do it per-file, that doesn't matter. The important thing is that you need to correctly resolve the path that comes from the watch service.

Of course. I have not yet tested but will do in few minutes.
I am currently adding the map of map, I believe having a map per model 'like you proposed) to retrieve all objects of a model may be useful one day.

@lolodomo
Copy link
Contributor Author

lolodomo commented Jul 9, 2023

In fact, it is not exactly per model but rather per directory. I will return to a simple map per file.

@lolodomo
Copy link
Contributor Author

lolodomo commented Dec 9, 2023

The problem is certainly that the semantic service or the semantics metadata provider is started while the tag registry does not yet include tags provided by file.
I remember I did something special in the registry to be sure that the managed custom tags will be loaded before.
I can probably not apply the same thing for file provider because we are in two different bundles and I imagine I will introduce a cyclic dependency if I do that.

@J-N-K
Copy link
Member

J-N-K commented Dec 9, 2023

It seem that e.g. the SemanticMetadataProvider does not implement a change listener for the SemanticTagRegistry, so tags added at a later stage are just ignored (until the item changes and processItem is called again). IMO the proper way would be to call allItemsChanged whenever the tag registry changes. This would also allow to completely remove the "wait for both providers" part on the SemanticTagRegistry. WDYT?

@lolodomo
Copy link
Contributor Author

lolodomo commented Dec 9, 2023

IMO the proper way would be to call allItemsChanged whenever the tag registry changes.

Yes and use the registry listener already available.

This would also allow to completely remove the "wait for both providers" part on the SemanticTagRegistry.

I am not sure about that...

@lolodomo
Copy link
Contributor Author

lolodomo commented Dec 9, 2023

IMO the proper way would be to call allItemsChanged whenever the tag registry changes.

Yes and use the registry listener already available.

Unfortunately, a class cannot implement RegistryChangeListener more than once with different arguments.
I can't add RegistryChangeListener in addition to ItemRegistryChangeListener.

Another option is to have a multiple reference to providers in SemanticsMetadataProvider and call allItemsChanged each time a provider is added or removed.
Edit: does not work because the new provider is not yet handled by the registry.

@J-N-K
Copy link
Member

J-N-K commented Dec 9, 2023

You can define a new interface for that SemanticTagRegistryChangeListener. We have that in other places, too.

@lolodomo
Copy link
Contributor Author

lolodomo commented Dec 9, 2023

You can define a new interface for that SemanticTagRegistryChangeListener. We have that in other places, too.

Yes, I am doing something that should work...

@lolodomo
Copy link
Contributor Author

lolodomo commented Dec 9, 2023

@J-N-K : I resolved the problem. Looks at the code please.

I will resume my tests.

@lolodomo
Copy link
Contributor Author

lolodomo commented Dec 9, 2023

@J-N-K : end of my tests, tests are successful. You can merge when you want.

@spacemanspiff2007
Copy link
Contributor

Would it be possible to use the uid as a key in the yaml file:

So this

tags:
  # Example of a new equipment
  - uid: Equipment_Curtain
    label: Curtain
  # Example of a new location
  - uid: Location_Indoor_Room_HomeCinemaRoom
    label: Home Cinema Room
    description: The room containing my home cinema system.
    synonyms:
      - Home theater room
      - TV room
      - Movie room

Will become this:

tags:
  # Example of a new equipment
  Equipment_Curtain:
    label: Curtain
  # Example of a new location
  Location_Indoor_Room_HomeCinemaRoom:
    label: Home Cinema Room
    description: The room containing my home cinema system.
    synonyms:
      - Home theater room
      - TV room
      - Movie room

That way it would align with the suggestions I've made here

@lolodomo
Copy link
Contributor Author

Would it be possible to use the uid as a key in the yaml file

I don't have a long history with YAML but I believe each element of a list must start with a -. So it would mean not using lists.
To match the suggested syntax, I have no idea how should look the java DTO for a correct parsing because you can't predict the object name.

@lolodomo
Copy link
Contributor Author

I suggest you study this change after 4.1 is released, we don't have enough time now I believe.

@spacemanspiff2007
Copy link
Contributor

I don't have a long history with YAML but I believe each element of a list must start with a -. So it would mean not using lists.

Correct, my suggestion is - instead of using a list - to use a mapping.

To match the suggested syntax, I have no idea how should look the java DTO for a correct parsing because you can't predict the object name.

You can't immediately de serialize the loaded file but have to implement some additional logic.
The most simple solution would be to just iterate over the mapping and pull down the object name into the object.
E.g. the second form gets transformed to the first form which then is used internally.

It's a trivial three to five liner - here in python

# validate the file input
file = validate_file_structure(file)

# pull down the uids
objects = []
for uid, obj_definition in file.tags.items():
    obj_def = obj_definition.copy()
    obj_def['uid]'] = uid
    objects.append(obj_def)

# now you can deserialize
deserialize_objects_from_dto(objects)

Using the uid as a key in a mapping is not much easier to write and makes the format much more compact, it also saves you some ambiguity checks (e.g. for duplicate UIDs) because duplicate key is an invalid yaml.

@lolodomo
Copy link
Contributor Author

lolodomo commented Dec 10, 2023

Please guys, nothing happened during 6 months and at 7 days before we freeze everything, you ask me "structural" changes ?
My free time yesterday was mainly spent to take into account good comments from @J-N-K and to run full tests again (and fix an issue).
I would like to spent my little free time during the next 6 days to something else, like for example helping to review PRs.

If the syntax is changed after 4.1 is released, this is not a problem. I don't think users will create thousands of custom tags and the change will be very easy to do, we will explain it to users.

@spacemanspiff2007
Copy link
Contributor

Please guys, nothing happened during 6 months and at 7 days before we freeze everything, you ask me "structural" changes ?

I've only seen the provided example this morning, I believe it was not there before.

I've made detailed suggestions for a file format 2 weeks ago and made suggestions on design guidelines.
Like you I'd hate this yaml configuration push going stale because I think it's a great enhancement for the users.

If the syntax is changed after 4.1 is released, this is not a problem. I don't think users will create thousands of custom tags and the change will be very easy to do, we will explain it to users.

I'm fine with that but then we have to communicate it accordingly and beforehand.
Like:
"There is a possibility to create tags through this file format but this file format will change with the next releases".

@lolodomo
Copy link
Contributor Author

lolodomo commented Dec 10, 2023

Like:
"There is a possibility to create tags through this file format but this file format will change with the next releases".

"There is a possibility to create tags through this file format but this file format will change slightly with the next releases."
To not afraid users and because it is true ;)
Something to add in the release notes.

@J-N-K
Copy link
Member

J-N-K commented Dec 10, 2023

While I agree that the format suggested by @spacemanspiff2007 is probably better, it is also more difficult to implement. Jackson can de-serialize a Map OOTB, but

Equipment_Curtain:
  label: Curtain
Location_Indoor_Room_HomeCinemaRoom:
  label: Home Cinema Room
  description: The room containing my home cinema system.

is serialized into a Map which has Equipment_Curtain and Location_Indoor_Room_HomeCinemaRoom as key and ONLY the label and description elements in the value. The UID is therefore ONLY the key, not part of the value, which is wrong. It requires either duplicating the key as uid in the object (which is bad design), a custom de-serializer or another form of post-processing. My suggestion would be to merge as-is. Since we can read and write YAML files, we can convert while upgrading from version 1 to version 2.

@lolodomo
Copy link
Contributor Author

My suggestion would be to merge as-is.

I like this suggestion :)

@spacemanspiff2007
Copy link
Contributor

The UID is therefore ONLY the key, not part of the value, which is wrong.

Just to nitpick - it's not wrong it's just not de-serializable into java objects out of the box.
The amount of information is still the same, it's just ordered differently to make editing easier.
So the workflow would be load yaml-> post process -> to object.
But we really should have this discussion in the yaml file format issue.

For this PR I have no objections, just that the upcoming file change is communicated with the feature announcement.

@J-N-K J-N-K added the enhancement An enhancement or new feature of the Core label Dec 10, 2023
@J-N-K J-N-K added this to the 4.1 milestone Dec 10, 2023
@J-N-K J-N-K merged commit 070de55 into openhab:main Dec 10, 2023
3 checks passed
@lolodomo lolodomo deleted the YAMLfile_semantic_tag_provider branch December 10, 2023 10:33
@lolodomo
Copy link
Contributor Author

It finally happens :) Thank you @J-N-K

lolodomo added a commit to lolodomo/openhab-distro that referenced this pull request Dec 10, 2023
Related to openhab/openhab-core#3659

Signed-off-by: Laurent Garnier <[email protected]>
J-N-K pushed a commit to openhab/openhab-distro that referenced this pull request Dec 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement An enhancement or new feature of the Core
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants