-
-
Notifications
You must be signed in to change notification settings - Fork 456
Implement a core sitemap registry #5004
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements a core sitemap registry that decouples sitemap handling from the model packages. It creates a central registry for sitemaps with providers for different sources (DSL sitemaps and UI-created sitemaps) and introduces a new org.openhab.core.sitemap
module with a factory-based approach for sitemap creation.
- Creates a central
SitemapRegistry
to manage sitemaps from different providers - Introduces a new
org.openhab.core.sitemap
module with interfaces and implementations - Refactors existing UI sitemap provider to use the new registry architecture
Reviewed Changes
Copilot reviewed 91 out of 91 changed files in this pull request and generated 8 comments.
File | Description |
---|---|
Feature/Karaf configurations | Updates feature definitions to include new sitemap module and remove model dependencies |
Test configurations | Adds sitemap module dependency to integration test bundles |
UI module refactoring | Updates UI components to use new sitemap interfaces instead of model classes |
New sitemap module | Introduces core sitemap interfaces, registry, factory, and implementations |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
try { | ||
uri = createURIFromString(uriString); | ||
uri = createURIFromString(uriString != null ? uriString : ""); |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing an empty string to createURIFromString when uriString is null may cause MalformedURLException or URISyntaxException. The original null check and early return should be preserved or handled appropriately.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will change that. The problem was there before but would generate a null pointer exception, as the model classes are not annotated.
|
||
private static final Pattern CONDITION_PATTERN = Pattern | ||
.compile("((?<item>[A-Za-z]\\w*)?\\s*(?<condition>==|!=|<=|>=|<|>))?\\s*(?<sign>\\+|-)?(?<state>.+)"); | ||
.compile("((?<item>[A-Za-z]\\w*)?\\s*(?<condition>==|!=|<=|>=|<|>))?\\s*(?<value>(\\+|-)?.+)"); |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex pattern change from separate 'sign' and 'state' groups to a single 'value' group may break existing condition parsing. The original pattern allowed for more structured parsing of conditions.
.compile("((?<item>[A-Za-z]\\w*)?\\s*(?<condition>==|!=|<=|>=|<|>))?\\s*(?<value>(\\+|-)?.+)"); | |
.compile("((?<item>[A-Za-z]\\w*)?\\s*(?<condition>==|!=|<=|>=|<|>))?\\s*(?<sign>\\+|-)?(?<state>.+)"); |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The separate sign read into a separate field was only there to cope with a DSL parser limitation. The full value is needed, so no need to replicate these separate fields in the core representation of a condition value.
mapping.setCmd(cmd != null ? cmd : ""); | ||
mapping.setReleaseCmd(releaseCmd); | ||
mapping.setLabel(label); | ||
mapping.setLabel(label != null ? label : ""); |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting empty strings as default values for cmd and label may cause issues if the consuming code expects null values for missing/unset commands and labels.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be fine.
mapping.setCmd(cmd != null ? cmd : ""); | ||
mapping.setReleaseCmd(releaseCmd); | ||
mapping.setLabel(label); | ||
mapping.setLabel(label != null ? label : ""); |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting empty strings as default values for cmd and label may cause issues if the consuming code expects null values for missing/unset commands and labels.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be fine.
if (sourceRules instanceof Collection<?> sourceRulesCollection) { | ||
for (Object sourceRule : sourceRulesCollection) { | ||
if (sourceRule instanceof String) { | ||
String argument = !key.equals("visibility") ? getRuleArgument(sourceRule.toString()) : null; |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The hardcoded string comparison with 'visibility' makes the code brittle. Consider using constants or an enum for rule types to improve maintainability.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I doubt it is worth externalizing just this string.
import org.osgi.service.component.annotations.Component; | ||
|
||
/** | ||
* The {@link SitemapFactoryImpl} implements the {@link SitemapRegistry} |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class comment incorrectly states that SitemapFactoryImpl implements SitemapRegistry, but it actually implements SitemapFactory.
* The {@link SitemapFactoryImpl} implements the {@link SitemapRegistry} | |
* The {@link SitemapFactoryImpl} implements the {@link SitemapFactory} |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
|
||
@Override | ||
public String getWidgetType() { | ||
return this.getClass().getInterfaces()[0].getSimpleName(); |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accessing the first interface without checking if any interfaces exist can cause ArrayIndexOutOfBoundsException. This implementation assumes the widget always implements exactly one interface in a specific order.
return this.getClass().getInterfaces()[0].getSimpleName(); | |
Class<?>[] interfaces = this.getClass().getInterfaces(); | |
if (interfaces.length > 0) { | |
return interfaces[0].getSimpleName(); | |
} else { | |
return this.getClass().getSimpleName(); | |
} |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it does assume it always implements the widget's interface and it is the first interface. I don't think these checks add much.
* | ||
* @param visibility | ||
*/ | ||
void setVisibility(List<Rule> visibility); |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameter name 'visiblity' should be 'visibility' to match the method name and consistent naming convention.
void setVisibility(List<Rule> visibility); | |
void setVisibility(List<Rule> visibility); |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameter name could be visibilityRules
There is a new package |
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
aa5094a
to
af8145b
Compare
@mherwege : this message just to inform you that I am not ignoring your PR at all, I will review it. |
List<ButtonDefinition> getButtons(); | ||
|
||
/** | ||
* Replace the button grid buttons a new list of buttons. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: "with" is missing
* | ||
* @return label color rules | ||
*/ | ||
List<Rule> getLabelColor(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rather getLabelColorRules and setLabelColorRules ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would be possible. But it would also mean the change in consuming classes is more then just changing the import. As much as possible, I kept the same method names from what they where before in the model, so I only had to change imports and do some cleanup. But now may be the right time to use more appropriate names as this is a major change anyway. @lolodomo What do you think? I left it as is for now.
* | ||
* @return value color rules | ||
*/ | ||
List<Rule> getValueColor(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rather getValueColorRules and setValueColorRules ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above
* | ||
* @return icon color rules | ||
*/ | ||
List<Rule> getIconColor(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rather getIconColorRules and setIconColorRules ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above
* | ||
* @return visibility rules | ||
*/ | ||
List<Rule> getVisibility(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rather getVisibilityRules and setVisibilityRules ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above
* Returns the sitemap name. | ||
* | ||
* @return sitemap name. | ||
* @see #setName(String) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All these @see in this interface looks like useless as they just reference them each other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ended there because they started as copies of the generated model classes, but will remove.
private List<Rule> labelColor = new CopyOnWriteArrayList<>(); | ||
private List<Rule> valueColor = new CopyOnWriteArrayList<>(); | ||
private List<Rule> iconColor = new CopyOnWriteArrayList<>(); | ||
private List<Rule> visibility = new CopyOnWriteArrayList<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest labelColorRules, valueColorRules, ... etc
|
||
@Override | ||
protected void notifyListenersAboutUpdatedElement(Sitemap oldElement, Sitemap element) { | ||
registryChangeListeners.forEach(listener -> listener.updated(oldElement, element)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no notify method to call in super class ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could probably be much more simple. As I ended up extending AbstractRegistry
, only the last 2 methods (addSitemapProver
and removeSitemapProvider
) are needed. AbstractRegistry
keeps track of the listeners itself. I will make the change that way, but will need to do a full test.
I started by reviewing org.openhab.core.sitemap. The only question I have is if you did not forget that it is a possible to have a default rule condition to set a particular value when none of the other conditions are valid. |
case Default defaultWidget: | ||
setWidgetPropertyFromComponentConfig(defaultWidget, component, "height"); | ||
break; | ||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you forgot the case of Switch to call addWidgetMappings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, thank you.
mapping.setCmd(cmd != null ? cmd : ""); | ||
mapping.setReleaseCmd(releaseCmd); | ||
mapping.setLabel(label); | ||
mapping.setLabel(label != null ? label : ""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency, you could do the same as in addWidgetButtons, that is testing if value is not null before calling setCmd and setLabel.
@Test | ||
public void getWidgetUnknownPageId() throws ItemNotFoundException { | ||
Sitemap sitemap = SitemapFactory.eINSTANCE.createSitemap(); | ||
Sitemap sitemap = sitemapFactoryMock.createSitemap(SITEMAP_NAME); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it normal to not have any when(sitemapFactoryMock.createSitemap) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand your question here. I can't create a sitemap for testing anymore the same way as before when it was using the model. So I reverted to a mock.
if (itemToBeSent != null) { | ||
String widgetTypeName = widget.eClass().getInstanceTypeName() | ||
.substring(widget.eClass().getInstanceTypeName().lastIndexOf(".") + 1); | ||
String widgetTypeName = widget.getClass().getSimpleName(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather widget.getWidgetType() ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missed this when creating the method in Widget
.
The behaviour should not have changed. |
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
With this PR, a central sitemap registry is created. This registry has providers that offer sitemaps provide sitemaps from different providers. The current ones are DSL sitemaps and UI created sitemaps. The code for these has been refactored to implement such providers.
The reasons for doing this are:
org.openhab.core.model.sitemap
, soorg.openhab.core.ui
depends on this model package.With the current structure, whatever we do when creating a new sitemap provider forces pulling the
org.openhab.core.model.sitemap
package directly or indirectly.It therefore seems more appropriate to decouple the internal sitemap representation from the DSL model and xtext code generation.
See for the background: #4945 (comment) and #4945 (comment)
This PR prepares for:
This PR is accompanied by the webui PR (openhab/openhab-webui#3349) that adapts the dependencies for BasicUI and CometVisu. Other UI's should not be concerned as they all work purely through the REST API and SSE events.
This is of course a major refactoring. Therefore there is a high risk and probability of some regressions and needs to be tested extensively. Code tests run through and my tests have not revealed issues so far. But I do expect there may be some.
@lolodomo I believe this is a better basis to continue work on YAML and DSL conversion afterwards.