diff --git a/tools/pom.xml b/tools/pom.xml index 14eab0fef..d427db4c4 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -19,6 +19,7 @@ archetype i18n-plugin + upgradetool diff --git a/tools/upgradetool/pom.xml b/tools/upgradetool/pom.xml new file mode 100644 index 000000000..874ed8d18 --- /dev/null +++ b/tools/upgradetool/pom.xml @@ -0,0 +1,123 @@ + + + + 4.0.0 + + + org.openhab.core.tools + org.openhab.core.reactor.tools + 4.0.0-SNAPSHOT + + + upgradetool + + jar + + openHAB Core :: Tools :: Upgrade tool + A tool for upgrading openHAB from 3.4 to 4.0 + + + + org.openhab.core.bundles + org.openhab.core + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.thing + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.storage.json + ${project.version} + + + commons-cli + commons-cli + 1.5.0 + + + org.slf4j + slf4j-simple + ${slf4j.version} + + + com.google.code.gson + gson + 2.9.1 + + + javax.measure + unit-api + 2.1.3 + + + si.uom + si-units + 2.1 + + + tech.units + indriya + 2.1.2 + + + org.eclipse.jdt + org.eclipse.jdt.annotation + 2.2.600 + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.3.0 + + + unpack-eea + + unpack + + + + + org.lastnpe.eea + eea-all + ${eea.version} + true + + + + + + + + maven-assembly-plugin + 3.4.2 + + + + org.openhab.core.tools.UpgradeTool + + + + jar-with-dependencies + + + + + make-assembly + + single + + package + + + + + + diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java new file mode 100644 index 000000000..19f009cd0 --- /dev/null +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/UpgradeTool.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.tools; + +import static org.openhab.core.tools.internal.Upgrader.*; + +import java.util.Set; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.tools.internal.Upgrader; + +/** + * The {@link UpgradeTool} is a tool for upgrading openHAB to mitigate breaking changes + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class UpgradeTool { + private static final Set LOG_LEVELS = Set.of("TRACE", "DEBUG", "INFO", "WARN", "ERROR"); + private static final String OPT_COMMAND = "command"; + private static final String OPT_DIR = "dir"; + private static final String OPT_LOG = "log"; + private static final String OPT_FORCE = "force"; + + private static Options getOptions() { + Options options = new Options(); + + options.addOption( + Option.builder().longOpt(OPT_DIR).desc("directory to process").numberOfArgs(1).required().build()); + options.addOption(Option.builder().longOpt(OPT_COMMAND).numberOfArgs(1).desc("command to execute").build()); + options.addOption(Option.builder().longOpt(OPT_LOG).numberOfArgs(1).desc("log verbosity").build()); + options.addOption(Option.builder().longOpt(OPT_FORCE).desc("force execution (even if already done)").build()); + + return options; + } + + public static void main(String[] args) { + Options options = getOptions(); + try { + CommandLine commandLine = new DefaultParser().parse(options, args); + + String loglevel = commandLine.hasOption(OPT_LOG) ? commandLine.getOptionValue(OPT_LOG).toUpperCase() + : "INFO"; + if (!LOG_LEVELS.contains(loglevel)) { + System.out.println("Allowed log-levels are " + LOG_LEVELS); + System.exit(0); + } + + System.setProperty(org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY, loglevel); + + String baseDir = commandLine.hasOption(OPT_DIR) ? commandLine.getOptionValue(OPT_DIR) : ""; + boolean force = commandLine.hasOption(OPT_FORCE) ? true : false; + + Upgrader upgrader = new Upgrader(baseDir, force); + if (commandLine.hasOption(ITEM_COPY_UNIT_TO_METADATA)) { + upgrader.itemCopyUnitToMetadata(); + } else if (commandLine.hasOption(LINK_UPGRADE_JS_PROFILE)) { + upgrader.linkUpgradeJsProfile(); + } + } catch (ParseException e) { + HelpFormatter formatter = new HelpFormatter(); + String commands = Set.of(ITEM_COPY_UNIT_TO_METADATA, LINK_UPGRADE_JS_PROFILE).toString(); + formatter.printHelp("upgradetool", "", options, "Available commands: " + commands, true); + } + + System.exit(0); + } +} diff --git a/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java new file mode 100644 index 000000000..0c896ad24 --- /dev/null +++ b/tools/upgradetool/src/main/java/org/openhab/core/tools/internal/Upgrader.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.tools.internal; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Objects; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.items.ManagedItemProvider; +import org.openhab.core.items.Metadata; +import org.openhab.core.items.MetadataKey; +import org.openhab.core.storage.json.internal.JsonStorage; +import org.openhab.core.thing.internal.link.ItemChannelLinkConfigDescriptionProvider; +import org.openhab.core.thing.link.ItemChannelLink; +import org.openhab.core.types.util.UnitUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link Upgrader} contains the implementation of the upgrade methods + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class Upgrader { + public static final String ITEM_COPY_UNIT_TO_METADATA = "itemCopyUnitToMetadata"; + public static final String LINK_UPGRADE_JS_PROFILE = "linkUpgradeJsProfile"; + + private final Logger logger = LoggerFactory.getLogger(Upgrader.class); + private final String baseDir; + private final boolean force; + private final JsonStorage upgradeRecords; + + public Upgrader(String baseDir, boolean force) { + this.baseDir = baseDir; + this.force = force; + + Path upgradeJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.tools.UpgradeTool"); + upgradeRecords = new JsonStorage<>(upgradeJsonDatabasePath.toFile(), null, 5, 0, 0, List.of()); + } + + private boolean checkUpgradeRecord(String key) { + UpgradeRecord upgradeRecord = upgradeRecords.get(key); + if (upgradeRecord != null && !force) { + logger.info("Already executed '{}' on {}. Use '--force' to execute it again.", key, + upgradeRecord.executionDate); + return false; + } + return true; + } + + public void itemCopyUnitToMetadata() { + if (checkUpgradeRecord(ITEM_COPY_UNIT_TO_METADATA)) { + return; + } + Path itemJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Item.json"); + Path metadataJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.items.Metadata.json"); + logger.info("Copying item unit from state description to metadata in database '{}'", itemJsonDatabasePath); + + if (!Files.isReadable(itemJsonDatabasePath)) { + logger.error("Cannot access item database '{}', check path and access rights.", itemJsonDatabasePath); + return; + } + if (!Files.isWritable(metadataJsonDatabasePath)) { + logger.error("Cannot access metadata database '{}', check path and access rights.", + metadataJsonDatabasePath); + return; + } + + JsonStorage itemStorage = new JsonStorage<>(itemJsonDatabasePath.toFile(), + null, 5, 0, 0, List.of()); + JsonStorage metadataStorage = new JsonStorage<>(metadataJsonDatabasePath.toFile(), null, 5, 0, 0, + List.of()); + + itemStorage.getKeys().forEach(itemName -> { + ManagedItemProvider.PersistedItem item = itemStorage.get(itemName); + if (item != null && item.itemType.startsWith("Number:")) { + if (metadataStorage.containsKey("unit" + ":" + itemName)) { + logger.debug("{}: already contains a 'unit' metadata, skipping it", itemName); + } else { + Metadata metadata = metadataStorage.get("stateDescription:" + itemName); + if (metadata == null) { + logger.debug("{}: Nothing to do, no state description found.", itemName); + } else { + String pattern = (String) metadata.getConfiguration().get("pattern"); + if (pattern.contains(UnitUtils.UNIT_PLACEHOLDER)) { + logger.warn( + "{}: State description contains unit place-holder '%unit%', check if 'unit' metadata is needed!", + itemName); + } else { + Unit stateDescriptionUnit = UnitUtils.parseUnit(pattern); + if (stateDescriptionUnit != null) { + String unit = stateDescriptionUnit.toString(); + MetadataKey defaultUnitMetadataKey = new MetadataKey("unit", itemName); + Metadata defaultUnitMetadata = new Metadata(defaultUnitMetadataKey, unit, null); + metadataStorage.put(defaultUnitMetadataKey.toString(), defaultUnitMetadata); + logger.info("{}: Wrote 'unit={}' to metadata.", itemName, unit); + } + } + } + } + } + }); + + metadataStorage.flush(); + upgradeRecords.put(ITEM_COPY_UNIT_TO_METADATA, new UpgradeRecord(ZonedDateTime.now())); + } + + public void linkUpgradeJsProfile() { + if (checkUpgradeRecord(LINK_UPGRADE_JS_PROFILE)) { + return; + } + + Path linkJsonDatabasePath = Path.of(baseDir, "jsondb", "org.openhab.core.thing.link.ItemChannelLink.json"); + logger.info("Upgrading JS profile configuration in database '{}'", linkJsonDatabasePath); + + if (!Files.isWritable(linkJsonDatabasePath)) { + logger.error("Cannot access link database '{}', check path and access rights.", linkJsonDatabasePath); + return; + } + JsonStorage linkStorage = new JsonStorage<>(linkJsonDatabasePath.toFile(), null, 5, 0, 0, + List.of()); + + List.copyOf(linkStorage.getKeys()).forEach(linkUid -> { + ItemChannelLink link = Objects.requireNonNull(linkStorage.get(linkUid)); + Configuration configuration = link.getConfiguration(); + String profileName = (String) configuration.get(ItemChannelLinkConfigDescriptionProvider.PARAM_PROFILE); + if ("transform:JS".equals(profileName)) { + String function = (String) configuration.get("function"); + if (function != null) { + configuration.put("toItemScript", function); + configuration.put("toHandlerScript", "|input"); + configuration.remove("function"); + configuration.remove("sourceFormat"); + + linkStorage.put(linkUid, link); + logger.info("{}: rewrote JS profile link to new format", linkUid); + } else { + logger.info("{}: link already has correct configuration", linkUid); + } + } + }); + + linkStorage.flush(); + upgradeRecords.put(LINK_UPGRADE_JS_PROFILE, new UpgradeRecord(ZonedDateTime.now())); + } + + private static class UpgradeRecord { + public final ZonedDateTime executionDate; + + public UpgradeRecord(ZonedDateTime executionDate) { + this.executionDate = executionDate; + } + } +}