UpgradeTool add semantics upgrader and OH version#5379
UpgradeTool add semantics upgrader and OH version#5379mherwege wants to merge 21 commits intoopenhab:mainfrom
Conversation
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]>
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
There was a problem hiding this comment.
Pull request overview
This pull request adds a semantic tag upgrader to address inconsistencies in the semantic model introduced by OH 5.0, and implements infrastructure for tracking openHAB versions to enable version-aware upgrader re-execution. The PR also includes several refactoring improvements to existing upgraders.
Changes:
- Added SemanticTagUpgrader to clean up custom semantic tags and item semantic tags affected by OH 5.0 changes
- Implemented version tracking system that stores OH version information and allows upgraders to specify target versions for conditional re-execution
- Refactored existing upgraders with improved code organization, added
@Sinceannotations, and minor code improvements
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| SemanticTagUpgrader.java | New upgrader that removes duplicate custom semantic tags, updates parent relationships, and cleans item semantic tags to fix OH 5.0 semantic model inconsistencies |
| UpgradeTool.java | Added version tracking infrastructure, version comparison logic, new --version CLI option, and VersionRecord/UpgradeRecord structures |
| Upgrader.java | Added getTargetVersion() interface method to allow upgraders to specify target versions |
| YamlConfigurationV1TagsUpgrader.java | Refactored to use YAMLMapper builder pattern and improved code organization |
| JSProfileUpgrader.java | Renamed variable for clarity and added @Since annotation |
| ItemUnitToMetadataUpgrader.java | Changed to wildcard static import and renamed variable for clarity |
| PersistenceUpgrader.java | Extracted updateConfig method and added @Since annotation |
| ScriptProfileUpgrader.java | Added @Since annotation |
| HomeAssistantAddonUpgrader.java | Added @Since annotation |
| HomieAddonUpgrader.java | Added @Since annotation |
| pom.xml | Added org.openhab.core.semantics dependency |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java
Outdated
Show resolved
Hide resolved
| return version1.compareTo(version2) < 0; | ||
| } | ||
|
|
There was a problem hiding this comment.
The version comparison logic uses simple string comparison which may not work correctly for all semantic version formats. For example, "5.10.0" would be considered less than "5.2.0" in lexicographic comparison. Consider using a proper semantic version comparison library or splitting and comparing version components numerically.
| return version1.compareTo(version2) < 0; | |
| } | |
| return compareSemanticVersions(version1, version2) < 0; | |
| } | |
| /** | |
| * Compares two semantic version strings numerically. | |
| * <p> | |
| * The versions are split on {@code '.'} and each component is compared as an integer. | |
| * Missing components are treated as {@code 0}. Non-numeric suffixes within a component are | |
| * ignored (e.g. {@code "3beta"} is treated as {@code 3}). | |
| * </p> | |
| * | |
| * @return a negative value if {@code versionA < versionB}, zero if equal, and a positive value otherwise. | |
| */ | |
| private static int compareSemanticVersions(String versionA, String versionB) { | |
| String[] partsA = versionA.split("\\\\."); | |
| String[] partsB = versionB.split("\\\\."); | |
| int length = Math.max(partsA.length, partsB.length); | |
| for (int i = 0; i < length; i++) { | |
| int partA = i < partsA.length ? parseVersionPart(partsA[i]) : 0; | |
| int partB = i < partsB.length ? parseVersionPart(partsB[i]) : 0; | |
| if (partA != partB) { | |
| return Integer.compare(partA, partB); | |
| } | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Parses a single numeric component of a version string, ignoring any non-digit suffix. | |
| * | |
| * @param part the version component (e.g. {@code "10"}, {@code "3beta"}) | |
| * @return the numeric value of the leading digits, or {@code 0} if none are present or parsing fails | |
| */ | |
| private static int parseVersionPart(String part) { | |
| int endIndex = 0; | |
| int length = part.length(); | |
| while (endIndex < length && Character.isDigit(part.charAt(endIndex))) { | |
| endIndex++; | |
| } | |
| if (endIndex == 0) { | |
| return 0; | |
| } | |
| try { | |
| return Integer.parseInt(part.substring(0, endIndex)); | |
| } catch (NumberFormatException e) { | |
| return 0; | |
| } | |
| } |
There was a problem hiding this comment.
My comparison indeed was not correct. To avoid issues with comparing versions, I prefer using org.apache.maven.artifact.versioning.ComparableVersion instead.
Signed-off-by: Mark Herwege <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java:355
- The fallback logic in lastExecutedVersion has a flaw: it falls back to checking the UPGRADE_TOOL_VERSION_KEY in ohVersionRecords (lines 347-353) even when the upgradeRecord exists but executionVersion is null. This means that if an upgrader was executed before version tracking was implemented (executionVersion would be null), the method will return the global OH version instead of null. This could cause the upgrader to incorrectly skip execution. The fallback should only apply when upgradeRecord itself is null, not when executionVersion is null.
private static @Nullable String lastExecutedVersion(String upgrader) {
JsonStorage<UpgradeRecord> upgrades = upgradeRecords;
if (upgrades != null) {
UpgradeRecord upgradeRecord = upgrades.get(upgrader);
if (upgradeRecord != null) {
return upgradeRecord.executionVersion();
}
}
JsonStorage<VersionRecord> versions = ohVersionRecords;
if (versions != null) {
VersionRecord versionRecord = versions.get(UPGRADE_TOOL_VERSION_KEY);
if (versionRecord != null) {
return versionRecord.core();
}
}
return null;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/ItemUnitToMetadataUpgrader.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
...gradetool/src/main/java/org/openhab/core/tools/internal/YamlConfigurationV1TagsUpgrader.java
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Mark Herwege <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Show resolved
Hide resolved
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/PersistenceUpgrader.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/JSProfileUpgrader.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/HomieAddonUpgrader.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/HomeAssistantAddonUpgrader.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/ScriptProfileUpgrader.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Mark Herwege <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Outdated
Show resolved
Hide resolved
| .findAndAddModules() // | ||
| .visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) // | ||
| .visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) // | ||
| .defaultPropertyInclusion(Value.construct(Include.NON_NULL, Include.ALWAYS)) // | ||
| .enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN) // |
There was a problem hiding this comment.
The YAML mapper configuration here is intended to match YamlModelRepositoryImpl, but the inclusion settings differ: YamlModelRepositoryImpl uses setSerializationInclusion(Include.NON_NULL) (applies to both property values and container contents), while defaultPropertyInclusion(Value.construct(Include.NON_NULL, Include.ALWAYS)) keeps nulls in container contents. This can change the serialized YAML output. Align the inclusion settings with YamlModelRepositoryImpl to avoid behavioral differences.
There was a problem hiding this comment.
There was a change in behaviour between Jackson 2.8 and 2.9 that changed the behavior of setSerializationInclusion(Include.NON_NULL) to also remove nulls in container contents. I have changed it to match the Jackson 2.9 and later behavior. This behavior change may have gone unnoticed in the code base.
Signed-off-by: Mark Herwege <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Outdated
Show resolved
Hide resolved
| private static VersionRecord getTargetVersion(@Nullable Path userdataPath) { | ||
| if (userdataPath != null) { | ||
| Path versionFilePath = userdataPath.resolve(Path.of("etc", "version.properties")); | ||
| try (BufferedReader reader = Files.newBufferedReader(versionFilePath, StandardCharsets.UTF_8)) { | ||
| String build = null; | ||
| String distro = null; | ||
| String core = null; | ||
| String addons = null; | ||
| String karaf = null; | ||
|
|
||
| String line; | ||
| while ((line = reader.readLine()) != null) { | ||
| Matcher matcher = BUILD_VERSION_PATTERN.matcher(line.trim()); | ||
| if (matcher.matches()) { | ||
| build = matcher.group(1); | ||
| continue; | ||
| } | ||
| matcher = DISTRO_VERSION_PATTERN.matcher(line.trim()); | ||
| if (matcher.matches()) { | ||
| distro = matcher.group(1); | ||
| continue; | ||
| } | ||
| matcher = CORE_VERSION_PATTERN.matcher(line.trim()); | ||
| if (matcher.matches()) { | ||
| core = matcher.group(1); | ||
| continue; | ||
| } | ||
| matcher = ADDONS_VERSION_PATTERN.matcher(line.trim()); | ||
| if (matcher.matches()) { | ||
| addons = matcher.group(1); | ||
| continue; | ||
| } | ||
| matcher = KARAF_VERSION_PATTERN.matcher(line.trim()); | ||
| if (matcher.matches()) { | ||
| karaf = matcher.group(1); | ||
| continue; | ||
| } | ||
| } | ||
| return new VersionRecord(build, distro, core, addons, karaf); | ||
| } catch (IOException | SecurityException e) { | ||
| LOGGER.warn( | ||
| "Cannot retrieve OH core version from '{}' file. Some tasks may fail. You can provide the target version through the --version option.", | ||
| versionFilePath); | ||
| } | ||
| } | ||
| return new VersionRecord(); | ||
| } |
There was a problem hiding this comment.
getTargetVersion() parses etc/version.properties using regexes that expect key: value lines, but the runtime codebase reads this file with java.util.Properties (i.e., key=value, e.g. build-no=...). As a result core will typically stay null without throwing, so version tracking and the per-upgrader target-version rerun logic won’t work (and no warning is logged). Consider switching to Properties.load(...) (as in OpenHAB.buildString() / FeatureInstaller) or at least accepting both '=' and ':' separators, and warn when openhab-core cannot be extracted.
There was a problem hiding this comment.
The file parsed is not a real properties file. Still, as it is the pattern used in OpenHAB.java, I adjust to use the same pattern. It simplifies the code avoiding the regex constants.
Signed-off-by: Mark Herwege <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java
Outdated
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/internal/SemanticTagUpgrader.java
Show resolved
Hide resolved
tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Mark Herwege <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Copilot finally looks happy and build succeeds. |
Closes #5363
OH 5.0 introduced additional semantic tags and moved some tags between Point and Property. For some users, this is leading to an inconsistent semantic model that is not always easy to correct directly in the UI. Double semantic tags for one class where not visible and cannot be corrected in the UI (the UI only showed one).
Secondly, when custom tags where built as children of these moved tags, the parentID's in the custom tag database did not match anymore, leading to issues.
The semantic tag upgrader will, for managed custom semantic tags and managed items:
This is not necessarily making the model fully consistent again (e.g. it is still possible to have both an Equipment and a Point tag), but the remaining issues would be visible in the logs and the health checks, and can be corrected in the UI. As there are always assumptions to be made when we would automatically remove tags, I did not do that, but leave it to the user. The changes are the minimum changes to make it possible to edit the semantic tags through the UI again.
As I foresee that this upgrader may have to run again in future versions of OH, when new tags are added, I put in place infrastructure to have a target version for an upgrader. Upgraders will now store the OH version they run with and this can be checked against the target version to see if an upgrader should be run again (on top of the execution date which was already stored). This will make it possible to just change the target version in the upgrader to run again in a future version upgrade.
And as I am picking up the OH version the upgrader runs with, I store that version information in a separate file in the JSON storage. I see potential to use that in the future to keep track of the JSON DB version vs the OH version and make installation/upgrade decisions based on that (see discussion in https://community.openhab.org/t/openhab-reliability-and-upgrade-experience/167835/72). Having the version of the DB stored in the DB would be requirement for that. There may be a need for multiple (e.g. UpgradeTool here stores a version but it may not have run, so no version from there, a fresh installation should also have a version that could be written in the same file, OH startup would also have a version...). I put in the "UpgradeTool" key to be able to store multiple and decide on what to do based on that.